CAMEL-7782 Added camel-netty4-http component
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/db88eeda Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/db88eeda Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/db88eeda Branch: refs/heads/master Commit: db88eeda8e7c40b00b9f9ff015ed0b54e6922ea9 Parents: 8c5529f Author: Willem Jiang <willem.ji...@gmail.com> Authored: Fri Sep 5 20:55:54 2014 +0800 Committer: Willem Jiang <willem.ji...@gmail.com> Committed: Fri Sep 5 20:55:54 2014 +0800 ---------------------------------------------------------------------- .../apache/camel/impl/DefaultCamelContext.java | 2 + components/camel-netty4-http/pom.xml | 86 +++ .../netty4/http/ContextPathMatcher.java | 55 ++ .../netty4/http/DefaultContextPathMatcher.java | 88 +++ .../netty4/http/DefaultNettyHttpBinding.java | 550 +++++++++++++++++++ .../http/DefaultNettySharedHttpServer.java | 132 +++++ .../netty4/http/HttpClientPipelineFactory.java | 165 ++++++ .../component/netty4/http/HttpPrincipal.java | 52 ++ .../netty4/http/HttpServerBootstrapFactory.java | 104 ++++ .../http/HttpServerConsumerChannelFactory.java | 63 +++ .../netty4/http/HttpServerPipelineFactory.java | 172 ++++++ .../http/HttpServerSharedPipelineFactory.java | 159 ++++++ .../netty4/http/JAASSecurityAuthenticator.java | 72 +++ .../http/NettyChannelBufferStreamCache.java | 99 ++++ .../component/netty4/http/NettyHttpBinding.java | 119 ++++ .../netty4/http/NettyHttpComponent.java | 303 ++++++++++ .../netty4/http/NettyHttpConfiguration.java | 166 ++++++ .../netty4/http/NettyHttpConstants.java | 31 ++ .../netty4/http/NettyHttpConsumer.java | 75 +++ .../netty4/http/NettyHttpConverter.java | 118 ++++ .../netty4/http/NettyHttpEndpoint.java | 198 +++++++ .../http/NettyHttpHeaderFilterStrategy.java | 53 ++ .../component/netty4/http/NettyHttpHelper.java | 243 ++++++++ .../component/netty4/http/NettyHttpMessage.java | 51 ++ .../http/NettyHttpOperationFailedException.java | 77 +++ .../netty4/http/NettyHttpProducer.java | 120 ++++ .../http/NettyHttpSecurityConfiguration.java | 115 ++++ .../netty4/http/NettySharedHttpServer.java | 74 +++ ...ySharedHttpServerBootstrapConfiguration.java | 50 ++ .../netty4/http/RestContextPathMatcher.java | 102 ++++ .../netty4/http/RestNettyHttpBinding.java | 92 ++++ .../netty4/http/SecurityAuthenticator.java | 76 +++ .../http/SecurityAuthenticatorSupport.java | 127 +++++ .../netty4/http/SecurityConstraint.java | 31 ++ .../netty4/http/SecurityConstraintMapping.java | 133 +++++ .../http/handlers/HttpClientChannelHandler.java | 46 ++ .../http/handlers/HttpServerChannelHandler.java | 316 +++++++++++ .../HttpServerMultiplexChannelHandler.java | 235 ++++++++ .../src/main/resources/META-INF/LICENSE.txt | 203 +++++++ .../src/main/resources/META-INF/NOTICE.txt | 11 + .../services/org/apache/camel/TypeConverter | 18 + .../org/apache/camel/component/netty4-http | 17 + .../component/netty4/http/BaseNettyTest.java | 95 ++++ .../netty4/http/ManagedNettyEndpointTest.java | 81 +++ .../component/netty4/http/MyLoginModule.java | 102 ++++ .../component/netty4/http/MyRolePrincipal.java | 33 ++ .../netty4/http/NettyHttp500ErrorTest.java | 66 +++ ...yHttp500ErrorThrowExceptionOnServerTest.java | 67 +++ ...ttpAccessHttpRequestAndResponseBeanTest.java | 74 +++ .../NettyHttpAccessHttpRequestBeanTest.java | 54 ++ .../http/NettyHttpAccessHttpRequestTest.java | 59 ++ .../NettyHttpBasicAuthConstraintMapperTest.java | 96 ++++ ...asicAuthCustomSecurityAuthenticatorTest.java | 105 ++++ .../netty4/http/NettyHttpBasicAuthTest.java | 104 ++++ ...ndingPreservePostFormUrlEncodedBodyTest.java | 68 +++ .../http/NettyHttpBridgeEncodedPathTest.java | 66 +++ ...NettyHttpBridgeRouteUsingHttpClientTest.java | 92 ++++ .../http/NettyHttpCharacterEncodingTest.java | 63 +++ .../netty4/http/NettyHttpClientChunkedTest.java | 46 ++ .../http/NettyHttpClientExpectContinueTest.java | 58 ++ ...ponentConfigurationAndDocumentationTest.java | 57 ++ .../netty4/http/NettyHttpContentTypeTest.java | 87 +++ ...ettyHttpConvertPayloadToInputStreamTest.java | 62 +++ ...dpointUriCustomHeaderFilterStrategyTest.java | 70 +++ .../NettyHttpEndpointUriEncodingIssueTest.java | 57 ++ ...ntUriEncodingIssueUrlDecodeDisabledTest.java | 49 ++ .../http/NettyHttpFilterCamelHeadersTest.java | 76 +++ .../NettyHttpGetWithInvalidMessageTest.java | 106 ++++ ...ttyHttpGetWithParamAsExchangeHeaderTest.java | 127 +++++ .../netty4/http/NettyHttpGetWithParamTest.java | 78 +++ .../netty4/http/NettyHttpHandle404Test.java | 92 ++++ .../netty4/http/NettyHttpHeaderCaseTest.java | 73 +++ ...ettyHttpHeaderFilterStrategyRemovalTest.java | 78 +++ .../http/NettyHttpHeaderFilterStrategyTest.java | 106 ++++ .../netty4/http/NettyHttpHeadersTest.java | 54 ++ .../http/NettyHttpMapHeadersFalseTest.java | 70 +++ .../http/NettyHttpMethodRestrictTest.java | 74 +++ .../http/NettyHttpOnExceptionHandledTest.java | 56 ++ ...tpProducerBridgePathWithSpacesAtEndTest.java | 54 ++ .../http/NettyHttpProducerBridgeTest.java | 50 ++ .../http/NettyHttpProducerConcurrentTest.java | 87 +++ .../http/NettyHttpProducerKeepAliveTest.java | 64 +++ .../http/NettyHttpProducerQueryParamTest.java | 75 +++ .../NettyHttpProducerSendEmptyHeaderTest.java | 48 ++ .../http/NettyHttpProducerSimpleGetTest.java | 69 +++ .../http/NettyHttpProducerSimpleTest.java | 76 +++ ...ttpProducerTwoParametersWithSameKeyTest.java | 100 ++++ .../http/NettyHttpProducerWithHeaderTest.java | 60 ++ .../netty4/http/NettyHttpRawQueryTest.java | 57 ++ .../http/NettyHttpRedirectNoLocationTest.java | 59 ++ .../netty4/http/NettyHttpRedirectTest.java | 56 ++ .../http/NettyHttpRequestTimeoutTest.java | 62 +++ ...ReturnDataNotInputStreamConvertableTest.java | 53 ++ .../netty4/http/NettyHttpReturnFaultTest.java | 58 ++ .../component/netty4/http/NettyHttpSSLTest.java | 109 ++++ ...ettyHttpSameHostDifferentParametersTest.java | 52 ++ ...HttpSimpleBasicAuthConstraintMapperTest.java | 90 +++ .../http/NettyHttpSimpleBasicAuthTest.java | 70 +++ .../netty4/http/NettyHttpSimpleTest.java | 46 ++ .../http/NettyHttpSimpleUriParametersTest.java | 46 ++ .../NettyHttpStreamCacheFileResponseTest.java | 75 +++ .../http/NettyHttpSuspendResume503Test.java | 74 +++ .../netty4/http/NettyHttpSuspendResumeTest.java | 73 +++ .../netty4/http/NettyHttpTraceDisabledTest.java | 62 +++ .../http/NettyHttpTransferExceptionTest.java | 52 ++ ...HttpTwoRoutesBootstrapConfigurationTest.java | 93 ++++ .../NettyHttpTwoRoutesMatchOnUriPrefixTest.java | 77 +++ .../NettyHttpTwoRoutesStopOneRouteTest.java | 77 +++ .../netty4/http/NettyHttpTwoRoutesTest.java | 54 ++ ...outesValidateBootstrapConfigurationTest.java | 52 ++ .../http/NettyHttpXMLXPathResponseTest.java | 53 ++ .../netty4/http/NettyHttpXMLXPathTest.java | 53 ++ .../http/NettyRecipientListHttpBaseTest.java | 53 ++ .../netty4/http/NettyRouteSimpleTest.java | 51 ++ .../netty4/http/NettySharedHttpServerTest.java | 90 +++ .../http/NettyUseRawHttpResponseTest.java | 63 +++ .../http/SecurityConstraintMappingTest.java | 108 ++++ .../http/SpringNettyHttpBasicAuthTest.java | 118 ++++ .../netty4/http/SpringNettyHttpSSLTest.java | 79 +++ .../component/netty4/http/rest/CountryPojo.java | 40 ++ ...estNettyHttpBindingModeAutoWithJsonTest.java | 59 ++ ...RestNettyHttpBindingModeAutoWithXmlTest.java | 59 ++ .../rest/RestNettyHttpBindingModeJsonTest.java | 76 +++ .../rest/RestNettyHttpBindingModeXmlTest.java | 77 +++ .../RestNettyHttpContextPathMatchGetTest.java | 68 +++ .../netty4/http/rest/RestNettyHttpGetTest.java | 56 ++ .../http/rest/RestNettyHttpPojoInOutTest.java | 53 ++ .../rest/RestNettyHttpPostJsonJaxbPojoTest.java | 61 ++ .../rest/RestNettyHttpPostJsonPojoListTest.java | 68 +++ .../rest/RestNettyHttpPostJsonPojoTest.java | 61 ++ .../rest/RestNettyHttpPostXmlJaxbPojoTest.java | 79 +++ .../netty4/http/rest/RestPathMatchingTest.java | 86 +++ .../netty4/http/rest/UserJaxbPojo.java | 48 ++ .../component/netty4/http/rest/UserPojo.java | 40 ++ .../component/netty4/http/rest/UserService.java | 33 ++ .../src/test/resources/jsse/localhost.ks | Bin 0 -> 1265 bytes .../src/test/resources/log4j.properties | 39 ++ .../src/test/resources/myjaas.config | 5 + .../http/SpringNettyHttpBasicAuthTest.xml | 70 +++ .../netty4/http/SpringNettyHttpSSLTest.xml | 62 +++ components/pom.xml | 1 + parent/pom.xml | 5 + 142 files changed, 11644 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java b/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java index f825a25..7dd681d 100644 --- a/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java +++ b/camel-core/src/main/java/org/apache/camel/impl/DefaultCamelContext.java @@ -1101,6 +1101,8 @@ public class DefaultCamelContext extends ServiceSupport implements ModelCamelCon return "atmosphere/websocket"; } else if ("netty-http".equals(componentName)) { return "netty/http"; + } else if ("netty4-http".equals(componentName)) { + return "netty4/http"; } return componentName.replaceAll("-", ""); } http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/pom.xml b/components/camel-netty4-http/pom.xml new file mode 100644 index 0000000..47e8269 --- /dev/null +++ b/components/camel-netty4-http/pom.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version + 2.0 (the "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 Unless required by + applicable law or agreed to in writing, software distributed under the + License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. See the License for + the specific language governing permissions and limitations under the + License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel</groupId> + <artifactId>components</artifactId> + <version>2.14-SNAPSHOT</version> + </parent> + + <artifactId>camel-netty4-http</artifactId> + <packaging>bundle</packaging> + <name>Camel :: Netty4 HTTP</name> + <description>Camel Netty4 HTTP support</description> + + <properties> + <camel.osgi.export.pkg> + org.apache.camel.component.netty4.http.* + </camel.osgi.export.pkg> + <camel.osgi.export.service>org.apache.camel.spi.ComponentResolver;component=netty4-http</camel.osgi.export.service> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-netty4</artifactId> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-codec-http</artifactId> + <version>${netty-version}</version> + </dependency> + + <!-- testing --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-spring</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-http</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <!-- for testing rest-dsl --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-jackson</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-jaxb</artifactId> + <scope>test</scope> + </dependency> + + <!-- logging --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + <scope>test</scope> + </dependency> + + </dependencies> + +</project> http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/ContextPathMatcher.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/ContextPathMatcher.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/ContextPathMatcher.java new file mode 100644 index 0000000..1b58af8 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/ContextPathMatcher.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.netty4.http; + +/** + * A matcher used for selecting the correct {@link org.apache.camel.component.netty.http.handlers.HttpServerChannelHandler} + * to handle an incoming {@link io.netty.handler.codec.http.HttpRequest} when you use multiple routes on the same + * port. + * <p/> + * As when we do that, we need to multiplex and select the correct consumer route to process the HTTP request. + * To do that we need to match on the incoming HTTP request context-path from the request. + */ +public interface ContextPathMatcher { + + /** + * Whether the target context-path matches a regular url. + * + * @param path the context-path from the incoming HTTP request + * @return <tt>true</tt> to match, <tt>false</tt> if not. + */ + boolean matches(String path); + + /** + * Whether the target context-path matches a REST url. + * + * @param path the context-path from the incoming HTTP request + * @param wildcard whether to match strict or by wildcards + * @return <tt>true</tt> to match, <tt>false</tt> if not. + */ + boolean matchesRest(String path, boolean wildcard); + + /** + * Matches the given request HTTP method with the configured HTTP method of the consumer + * + * @param method the request HTTP method + * @param restrict the consumer configured HTTP restrict method + * @return <tt>true</tt> if matched, <tt>false</tt> otherwise + */ + boolean matchMethod(String method, String restrict); + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultContextPathMatcher.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultContextPathMatcher.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultContextPathMatcher.java new file mode 100644 index 0000000..f30cc66 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultContextPathMatcher.java @@ -0,0 +1,88 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.netty4.http; + +import java.util.Locale; + +/** + * A default {@link ContextPathMatcher} which supports the <tt>matchOnUriPrefix</tt> option. + */ +public class DefaultContextPathMatcher implements ContextPathMatcher { + + private final String path; + private final boolean matchOnUriPrefix; + + public DefaultContextPathMatcher(String path, boolean matchOnUriPrefix) { + this.path = path.toLowerCase(Locale.US); + this.matchOnUriPrefix = matchOnUriPrefix; + } + + @Override + public boolean matches(String path) { + path = path.toLowerCase(Locale.US); + if (!matchOnUriPrefix) { + // exact match + return path.equals(this.path); + } else { + // match on prefix, then we just need to match the start of the context-path + return path.startsWith(this.path); + } + } + + @Override + public boolean matchesRest(String path, boolean wildcard) { + return false; + } + + @Override + public boolean matchMethod(String method, String restrict) { + // always match as HttpServerChannelHandler will deal with HTTP method restrictions + return true; + } + + public String getPath() { + return path; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DefaultContextPathMatcher that = (DefaultContextPathMatcher) o; + + if (matchOnUriPrefix != that.matchOnUriPrefix) { + return false; + } + if (!path.equals(that.path)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = path.hashCode(); + result = 31 * result + (matchOnUriPrefix ? 1 : 0); + return result; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultNettyHttpBinding.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultNettyHttpBinding.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultNettyHttpBinding.java new file mode 100644 index 0000000..42e4bec --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultNettyHttpBinding.java @@ -0,0 +1,550 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.netty4.http; + +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.DefaultHttpRequest; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.NoTypeConversionAvailableException; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.TypeConverter; +import org.apache.camel.component.netty4.NettyConverter; +import org.apache.camel.spi.HeaderFilterStrategy; +import org.apache.camel.util.ExchangeHelper; +import org.apache.camel.util.IOHelper; +import org.apache.camel.util.MessageHelper; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.URISupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Default {@link NettyHttpBinding}. + */ +public class DefaultNettyHttpBinding implements NettyHttpBinding, Cloneable { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultNettyHttpBinding.class); + private HeaderFilterStrategy headerFilterStrategy = new NettyHttpHeaderFilterStrategy(); + + public DefaultNettyHttpBinding() { + } + + public DefaultNettyHttpBinding(HeaderFilterStrategy headerFilterStrategy) { + this.headerFilterStrategy = headerFilterStrategy; + } + + public DefaultNettyHttpBinding copy() { + try { + return (DefaultNettyHttpBinding)this.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeCamelException(e); + } + } + + @Override + public Message toCamelMessage(FullHttpRequest request, Exchange exchange, NettyHttpConfiguration configuration) throws Exception { + LOG.trace("toCamelMessage: {}", request); + + NettyHttpMessage answer = new NettyHttpMessage(request, null); + answer.setExchange(exchange); + if (configuration.isMapHeaders()) { + populateCamelHeaders(request, answer.getHeaders(), exchange, configuration); + } + + if (configuration.isDisableStreamCache()) { + // keep the body as is, and use type converters + answer.setBody(request.content()); + } else { + // turn the body into stream cached + NettyChannelBufferStreamCache cache = new NettyChannelBufferStreamCache(request.content()); + answer.setBody(cache); + } + return answer; + } + + @Override + public void populateCamelHeaders(FullHttpRequest request, Map<String, Object> headers, Exchange exchange, NettyHttpConfiguration configuration) throws Exception { + LOG.trace("populateCamelHeaders: {}", request); + + // NOTE: these headers is applied using the same logic as camel-http/camel-jetty to be consistent + + headers.put(Exchange.HTTP_METHOD, request.getMethod().name()); + // strip query parameters from the uri + String s = request.getUri(); + if (s.contains("?")) { + s = ObjectHelper.before(s, "?"); + } + + // we want the full path for the url, as the client may provide the url in the HTTP headers as absolute or relative, eg + // /foo + // http://servername/foo + String http = configuration.isSsl() ? "https://" : "http://"; + if (!s.startsWith(http)) { + if (configuration.getPort() != 80) { + s = http + configuration.getHost() + ":" + configuration.getPort() + s; + } else { + s = http + configuration.getHost() + s; + } + } + + headers.put(Exchange.HTTP_URL, s); + // uri is without the host and port + URI uri = new URI(request.getUri()); + // uri is path and query parameters + headers.put(Exchange.HTTP_URI, uri.getPath()); + headers.put(Exchange.HTTP_QUERY, uri.getQuery()); + headers.put(Exchange.HTTP_RAW_QUERY, uri.getRawQuery()); + + // strip the starting endpoint path so the path is relative to the endpoint uri + String path = uri.getPath(); + if (configuration.getPath() != null && path.startsWith(configuration.getPath())) { + path = path.substring(configuration.getPath().length()); + } + headers.put(Exchange.HTTP_PATH, path); + + if (LOG.isTraceEnabled()) { + LOG.trace("HTTP-Method {}", request.getMethod().name()); + LOG.trace("HTTP-Uri {}", request.getUri()); + } + + for (String name : request.headers().names()) { + // mapping the content-type + if (name.toLowerCase(Locale.US).equals("content-type")) { + name = Exchange.CONTENT_TYPE; + } + + if (name.toLowerCase(Locale.US).equals("authorization")) { + String value = request.headers().get(name); + // store a special header that this request was authenticated using HTTP Basic + if (value != null && value.trim().startsWith("Basic")) { + if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(NettyHttpConstants.HTTP_AUTHENTICATION, "Basic", exchange)) { + NettyHttpHelper.appendHeader(headers, NettyHttpConstants.HTTP_AUTHENTICATION, "Basic"); + } + } + } + + // add the headers one by one, and use the header filter strategy + List<String> values = request.headers().getAll(name); + Iterator<?> it = ObjectHelper.createIterator(values); + while (it.hasNext()) { + Object extracted = it.next(); + Object decoded = shouldUrlDecodeHeader(configuration, name, extracted, "UTF-8"); + LOG.trace("HTTP-header: {}", extracted); + if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(name, decoded, exchange)) { + NettyHttpHelper.appendHeader(headers, name, decoded); + } + } + } + + // add uri parameters as headers to the Camel message + if (request.getUri().contains("?")) { + String query = ObjectHelper.after(request.getUri(), "?"); + Map<String, Object> uriParameters = URISupport.parseQuery(query); + + for (Map.Entry<String, Object> entry : uriParameters.entrySet()) { + String name = entry.getKey(); + Object values = entry.getValue(); + Iterator<?> it = ObjectHelper.createIterator(values); + while (it.hasNext()) { + Object extracted = it.next(); + Object decoded = shouldUrlDecodeHeader(configuration, name, extracted, "UTF-8"); + LOG.trace("URI-Parameter: {}", extracted); + if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(name, decoded, exchange)) { + NettyHttpHelper.appendHeader(headers, name, decoded); + } + } + } + } + + // if body is application/x-www-form-urlencoded then extract the body as query string and append as headers + // if it is a bridgeEndpoint we need to skip this part of work + if (request.getMethod().name().equals("POST") && request.headers().get(Exchange.CONTENT_TYPE) != null + && request.headers().get(Exchange.CONTENT_TYPE).startsWith(NettyHttpConstants.CONTENT_TYPE_WWW_FORM_URLENCODED) + && !configuration.isBridgeEndpoint()) { + + String charset = "UTF-8"; + + // Push POST form params into the headers to retain compatibility with DefaultHttpBinding + String body = request.content().toString(Charset.forName(charset)); + if (ObjectHelper.isNotEmpty(body)) { + for (String param : body.split("&")) { + String[] pair = param.split("=", 2); + if (pair.length == 2) { + String name = shouldUrlDecodeHeader(configuration, "", pair[0], charset); + String value = shouldUrlDecodeHeader(configuration, name, pair[1], charset); + if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(name, value, exchange)) { + NettyHttpHelper.appendHeader(headers, name, value); + } + } else { + throw new IllegalArgumentException("Invalid parameter, expected to be a pair but was " + param); + } + } + } + } + + } + + /** + * Decodes the header if needed to, or returns the header value as is. + * + * @param configuration the configuration + * @param headerName the header name + * @param value the current header value + * @param charset the charset to use for decoding + * @return the decoded value (if decoded was needed) or a <tt>toString</tt> representation of the value. + * @throws UnsupportedEncodingException is thrown if error decoding. + */ + protected String shouldUrlDecodeHeader(NettyHttpConfiguration configuration, String headerName, Object value, String charset) throws UnsupportedEncodingException { + // do not decode Content-Type + if (Exchange.CONTENT_TYPE.equals(headerName)) { + return value.toString(); + } else if (configuration.isUrlDecodeHeaders()) { + return URLDecoder.decode(value.toString(), charset); + } else { + return value.toString(); + } + } + + @Override + public Message toCamelMessage(FullHttpResponse response, Exchange exchange, NettyHttpConfiguration configuration) throws Exception { + LOG.trace("toCamelMessage: {}", response); + + NettyHttpMessage answer = new NettyHttpMessage(null, response); + answer.setExchange(exchange); + if (configuration.isMapHeaders()) { + populateCamelHeaders(response, answer.getHeaders(), exchange, configuration); + } + + // keep the body as is, and use type converters + answer.setBody(response.content()); + return answer; + } + + @Override + public void populateCamelHeaders(FullHttpResponse response, Map<String, Object> headers, Exchange exchange, NettyHttpConfiguration configuration) throws Exception { + LOG.trace("populateCamelHeaders: {}", response); + + headers.put(Exchange.HTTP_RESPONSE_CODE, response.getStatus().code()); + headers.put(NettyHttpConstants.HTTP_RESPONSE_TEXT, response.getStatus().reasonPhrase()); + + for (String name : response.headers().names()) { + // mapping the content-type + if (name.toLowerCase().equals("content-type")) { + name = Exchange.CONTENT_TYPE; + } + // add the headers one by one, and use the header filter strategy + List<String> values = response.headers().getAll(name); + Iterator<?> it = ObjectHelper.createIterator(values); + while (it.hasNext()) { + Object extracted = it.next(); + LOG.trace("HTTP-header: {}", extracted); + if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(name, extracted, exchange)) { + NettyHttpHelper.appendHeader(headers, name, extracted); + } + } + } + } + + @Override + public HttpResponse toNettyResponse(Message message, NettyHttpConfiguration configuration) throws Exception { + LOG.trace("toNettyResponse: {}", message); + + // the message body may already be a Netty HTTP response + if (message.getBody() instanceof HttpResponse) { + return (HttpResponse) message.getBody(); + } + + Object body = message.getBody(); + Exception cause = message.getExchange().getException(); + // support bodies as native Netty + ByteBuf buffer; + // the response code is 200 for OK and 500 for failed + boolean failed = message.getExchange().isFailed(); + int defaultCode = failed ? 500 : 200; + + int code = message.getHeader(Exchange.HTTP_RESPONSE_CODE, defaultCode, int.class); + + LOG.trace("HTTP Status Code: {}", code); + + + // if there was an exception then use that as body + if (cause != null) { + if (configuration.isTransferException()) { + // we failed due an exception, and transfer it as java serialized object + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(cause); + oos.flush(); + IOHelper.close(oos, bos); + + // the body should be the serialized java object of the exception + body = NettyConverter.toByteBuffer(bos.toByteArray()); + // force content type to be serialized java object + message.setHeader(Exchange.CONTENT_TYPE, NettyHttpConstants.CONTENT_TYPE_JAVA_SERIALIZED_OBJECT); + } else { + // we failed due an exception so print it as plain text + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + cause.printStackTrace(pw); + + // the body should then be the stacktrace + body = NettyConverter.toByteBuffer(sw.toString().getBytes()); + // force content type to be text/plain as that is what the stacktrace is + message.setHeader(Exchange.CONTENT_TYPE, "text/plain"); + } + + // and mark the exception as failure handled, as we handled it by returning it as the response + ExchangeHelper.setFailureHandled(message.getExchange()); + } + + if (body instanceof ByteBuf) { + buffer = (ByteBuf) body; + } else { + // try to convert to buffer first + buffer = message.getBody(ByteBuf.class); + if (buffer == null) { + // fallback to byte array as last resort + byte[] data = message.getBody(byte[].class); + if (data != null) { + buffer = NettyConverter.toByteBuffer(data); + } else { + // and if byte array fails then try String + String str; + if (body != null) { + str = message.getMandatoryBody(String.class); + } else { + str = ""; + } + buffer = NettyConverter.toByteBuffer(str.getBytes()); + } + } + } + + HttpResponse response = null; + + if (buffer != null) { + response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(code), buffer); + // We just need to reset the readerIndex this time + if (buffer.readerIndex() == buffer.writerIndex()) { + buffer.setIndex(0, buffer.writerIndex()); + } + // TODO How to enable the chunk transport + int len = buffer.readableBytes(); + // set content-length + response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, len); + LOG.trace("Content-Length: {}", len); + } else { + response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(code)); + } + + TypeConverter tc = message.getExchange().getContext().getTypeConverter(); + + // append headers + // must use entrySet to ensure case of keys is preserved + for (Map.Entry<String, Object> entry : message.getHeaders().entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + // use an iterator as there can be multiple values. (must not use a delimiter) + final Iterator<?> it = ObjectHelper.createIterator(value, null); + while (it.hasNext()) { + String headerValue = tc.convertTo(String.class, it.next()); + if (headerValue != null && headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToCamelHeaders(key, headerValue, message.getExchange())) { + LOG.trace("HTTP-Header: {}={}", key, headerValue); + response.headers().add(key, headerValue); + } + } + } + + // set the content type in the response. + String contentType = MessageHelper.getContentType(message); + if (contentType != null) { + // set content-type + response.headers().set(HttpHeaders.Names.CONTENT_TYPE, contentType); + LOG.trace("Content-Type: {}", contentType); + } + + // configure connection to accordingly to keep alive configuration + // favor using the header from the message + String connection = message.getHeader(HttpHeaders.Names.CONNECTION, String.class); + if (connection == null) { + // fallback and use the keep alive from the configuration + if (configuration.isKeepAlive()) { + connection = HttpHeaders.Values.KEEP_ALIVE; + } else { + connection = HttpHeaders.Values.CLOSE; + } + } + response.headers().set(HttpHeaders.Names.CONNECTION, connection); + LOG.trace("Connection: {}", connection); + + return response; + } + + @Override + public HttpRequest toNettyRequest(Message message, String uri, NettyHttpConfiguration configuration) throws Exception { + LOG.trace("toNettyRequest: {}", message); + + // the message body may already be a Netty HTTP response + if (message.getBody() instanceof HttpRequest) { + return (HttpRequest) message.getBody(); + } + + // just assume GET for now, we will later change that to the actual method to use + HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri); + + Object body = message.getBody(); + if (body != null) { + // support bodies as native Netty + ByteBuf buffer; + if (body instanceof ByteBuf) { + buffer = (ByteBuf) body; + } else { + // try to convert to buffer first + buffer = message.getBody(ByteBuf.class); + if (buffer == null) { + // fallback to byte array as last resort + byte[] data = message.getMandatoryBody(byte[].class); + buffer = NettyConverter.toByteBuffer(data); + } + } + if (buffer != null) { + request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uri, buffer); + int len = buffer.readableBytes(); + // set content-length + request.headers().set(HttpHeaders.Names.CONTENT_LENGTH, len); + LOG.trace("Content-Length: {}", len); + } else { + // we do not support this kind of body + throw new NoTypeConversionAvailableException(body, ByteBuf.class); + } + } + + // update HTTP method accordingly as we know if we have a body or not + HttpMethod method = NettyHttpHelper.createMethod(message, body != null); + request.setMethod(method); + + TypeConverter tc = message.getExchange().getContext().getTypeConverter(); + + // if we bridge endpoint then we need to skip matching headers with the HTTP_QUERY to avoid sending + // duplicated headers to the receiver, so use this skipRequestHeaders as the list of headers to skip + Map<String, Object> skipRequestHeaders = null; + if (configuration.isBridgeEndpoint()) { + String queryString = message.getHeader(Exchange.HTTP_QUERY, String.class); + if (queryString != null) { + skipRequestHeaders = URISupport.parseQuery(queryString); + } + // Need to remove the Host key as it should be not used + message.getHeaders().remove("host"); + } + + // append headers + // must use entrySet to ensure case of keys is preserved + for (Map.Entry<String, Object> entry : message.getHeaders().entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + + // we should not add headers for the parameters in the uri if we bridge the endpoint + // as then we would duplicate headers on both the endpoint uri, and in HTTP headers as well + if (skipRequestHeaders != null && skipRequestHeaders.containsKey(key)) { + continue; + } + + // use an iterator as there can be multiple values. (must not use a delimiter) + final Iterator<?> it = ObjectHelper.createIterator(value, null, true); + while (it.hasNext()) { + String headerValue = tc.convertTo(String.class, it.next()); + + if (headerValue != null && headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToCamelHeaders(key, headerValue, message.getExchange())) { + LOG.trace("HTTP-Header: {}={}", key, headerValue); + request.headers().add(key, headerValue); + } + } + } + + // set the content type in the response. + String contentType = MessageHelper.getContentType(message); + if (contentType != null) { + // set content-type + request.headers().set(HttpHeaders.Names.CONTENT_TYPE, contentType); + LOG.trace("Content-Type: {}", contentType); + } + + // must include HOST header as required by HTTP 1.1 + // use URI as its faster than URL (no DNS lookup) + URI u = new URI(uri); + String host = u.getHost(); + request.headers().set(HttpHeaders.Names.HOST, host); + LOG.trace("Host: {}", host); + + // configure connection to accordingly to keep alive configuration + // favor using the header from the message + String connection = message.getHeader(HttpHeaders.Names.CONNECTION, String.class); + if (connection == null) { + // fallback and use the keep alive from the configuration + if (configuration.isKeepAlive()) { + connection = HttpHeaders.Values.KEEP_ALIVE; + } else { + connection = HttpHeaders.Values.CLOSE; + } + } + request.headers().set(HttpHeaders.Names.CONNECTION, connection); + LOG.trace("Connection: {}", connection); + + return request; + } + + @Override + public HeaderFilterStrategy getHeaderFilterStrategy() { + return headerFilterStrategy; + } + + @Override + public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) { + this.headerFilterStrategy = headerFilterStrategy; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultNettySharedHttpServer.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultNettySharedHttpServer.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultNettySharedHttpServer.java new file mode 100644 index 0000000..3ad1fb1 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultNettySharedHttpServer.java @@ -0,0 +1,132 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.netty4.http; + +import java.util.concurrent.ThreadFactory; +import java.util.regex.Matcher; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import org.apache.camel.component.netty4.NettyServerBootstrapFactory; +import org.apache.camel.component.netty4.http.handlers.HttpServerMultiplexChannelHandler; +import org.apache.camel.spi.ClassResolver; +import org.apache.camel.support.ServiceSupport; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.ServiceHelper; +import org.apache.camel.util.concurrent.CamelThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A default {@link NettySharedHttpServer} to make sharing Netty server in Camel applications easier. + */ +public class DefaultNettySharedHttpServer extends ServiceSupport implements NettySharedHttpServer { + + // TODO: option to enlist in JMX + + public static final String DEFAULT_PATTERN = "Camel Thread ##counter# - #name#:#port#"; + private static final Logger LOG = LoggerFactory.getLogger(DefaultNettySharedHttpServer.class); + + private NettySharedHttpServerBootstrapConfiguration configuration; + private HttpServerConsumerChannelFactory channelFactory; + private HttpServerBootstrapFactory bootstrapFactory; + private ClassResolver classResolver; + private boolean startServer = true; + private String threadPattern = DEFAULT_PATTERN; + + public void setNettyServerBootstrapConfiguration(NettySharedHttpServerBootstrapConfiguration configuration) { + this.configuration = configuration; + } + + public void setClassResolver(ClassResolver classResolver) { + this.classResolver = classResolver; + } + + public int getPort() { + return configuration != null ? configuration.getPort() : -1; + } + + public HttpServerConsumerChannelFactory getConsumerChannelFactory() { + return channelFactory; + } + + public NettyServerBootstrapFactory getServerBootstrapFactory() { + return bootstrapFactory; + } + + public int getConsumersSize() { + if (channelFactory != null) { + return channelFactory.consumers(); + } else { + return -1; + } + } + + public void setStartServer(boolean startServer) { + this.startServer = startServer; + } + + public void setThreadNamePattern(String pattern) { + this.threadPattern = pattern; + } + + protected void doStart() throws Exception { + ObjectHelper.notNull(configuration, "setNettyServerBootstrapConfiguration() must be called with a NettyServerBootstrapConfiguration instance", this); + + // port must be set + if (configuration.getPort() <= 0) { + throw new IllegalArgumentException("Port must be configured on NettySharedHttpServerBootstrapConfiguration " + configuration); + } + // hostname must be set + if (ObjectHelper.isEmpty(configuration.getHost())) { + throw new IllegalArgumentException("Host must be configured on NettySharedHttpServerBootstrapConfiguration " + configuration); + } + + LOG.debug("NettySharedHttpServer using configuration: {}", configuration); + + // force using tcp as the underlying transport + configuration.setProtocol("tcp"); + + channelFactory = new HttpServerMultiplexChannelHandler(); + channelFactory.init(configuration.getPort()); + + ChannelInitializer<Channel> pipelineFactory = new HttpServerSharedPipelineFactory(configuration, channelFactory, classResolver); + + // thread factory and pattern + String port = Matcher.quoteReplacement("" + configuration.getPort()); + String pattern = threadPattern; + pattern = pattern.replaceFirst("#port#", port); + ThreadFactory tf = new CamelThreadFactory(pattern, "NettySharedHttpServer", true); + + // create bootstrap factory and disable compatible check as its shared among the consumers + bootstrapFactory = new HttpServerBootstrapFactory(channelFactory, false); + bootstrapFactory.init(tf, configuration, pipelineFactory); + + ServiceHelper.startServices(channelFactory); + + if (startServer) { + LOG.info("Starting NettySharedHttpServer on {}:{}", configuration.getHost(), configuration.getPort()); + ServiceHelper.startServices(bootstrapFactory); + } + } + + @Override + protected void doStop() throws Exception { + LOG.info("Stopping NettySharedHttpServer on {}:{}", configuration.getHost(), configuration.getPort()); + ServiceHelper.stopServices(bootstrapFactory, channelFactory); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpClientPipelineFactory.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpClientPipelineFactory.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpClientPipelineFactory.java new file mode 100644 index 0000000..cb6610c --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpClientPipelineFactory.java @@ -0,0 +1,165 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.netty4.http; + +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.timeout.ReadTimeoutHandler; +import org.apache.camel.component.netty4.ClientPipelineFactory; +import org.apache.camel.component.netty4.NettyConfiguration; +import org.apache.camel.component.netty4.NettyProducer; +import org.apache.camel.component.netty4.http.handlers.HttpClientChannelHandler; +import org.apache.camel.component.netty4.ssl.SSLEngineFactory; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link org.apache.camel.component.netty.ClientPipelineFactory} for the Netty HTTP client. + */ +public class HttpClientPipelineFactory extends ClientPipelineFactory { + + private static final Logger LOG = LoggerFactory.getLogger(HttpClientPipelineFactory.class); + protected NettyHttpConfiguration configuration; + private NettyHttpProducer producer; + private SSLContext sslContext; + + public HttpClientPipelineFactory() { + // default constructor needed + } + + public HttpClientPipelineFactory(NettyHttpProducer nettyProducer) { + this.producer = nettyProducer; + try { + this.sslContext = createSSLContext(producer); + } catch (Exception e) { + throw ObjectHelper.wrapRuntimeCamelException(e); + } + + if (sslContext != null) { + LOG.info("Created SslContext {}", sslContext); + } + configuration = nettyProducer.getConfiguration(); + } + + @Override + public ClientPipelineFactory createPipelineFactory(NettyProducer nettyProducer) { + return new HttpClientPipelineFactory((NettyHttpProducer) nettyProducer); + } + + @Override + protected void initChannel(Channel ch) throws Exception { + // create a new pipeline + ChannelPipeline pipeline = ch.pipeline(); + + SslHandler sslHandler = configureClientSSLOnDemand(); + if (sslHandler != null) { + //TODO must close on SSL exception + //sslHandler.setCloseOnSSLException(true); + LOG.debug("Client SSL handler configured and added as an interceptor against the ChannelPipeline: {}", sslHandler); + pipeline.addLast("ssl", sslHandler); + } + + pipeline.addLast("http", new HttpClientCodec()); + pipeline.addLast("aggregator", new HttpObjectAggregator(configuration.getChunkedMaxContentLength())); + + if (producer.getConfiguration().getRequestTimeout() > 0) { + if (LOG.isTraceEnabled()) { + LOG.trace("Using request timeout {} millis", producer.getConfiguration().getRequestTimeout()); + } + ChannelHandler timeout = new ReadTimeoutHandler(producer.getConfiguration().getRequestTimeout(), TimeUnit.MILLISECONDS); + pipeline.addLast("timeout", timeout); + } + + // handler to route Camel messages + pipeline.addLast("handler", new HttpClientChannelHandler(producer)); + + + } + + private SSLContext createSSLContext(NettyProducer producer) throws Exception { + NettyConfiguration configuration = producer.getConfiguration(); + + if (!configuration.isSsl()) { + return null; + } + + SSLContext answer; + + // create ssl context once + if (configuration.getSslContextParameters() != null) { + answer = configuration.getSslContextParameters().createSSLContext(); + } else { + if (configuration.getKeyStoreFile() == null && configuration.getKeyStoreResource() == null) { + LOG.debug("keystorefile is null"); + } + if (configuration.getTrustStoreFile() == null && configuration.getTrustStoreResource() == null) { + LOG.debug("truststorefile is null"); + } + if (configuration.getPassphrase().toCharArray() == null) { + LOG.debug("passphrase is null"); + } + + SSLEngineFactory sslEngineFactory; + if (configuration.getKeyStoreFile() != null || configuration.getTrustStoreFile() != null) { + sslEngineFactory = new SSLEngineFactory(); + answer = sslEngineFactory.createSSLContext(producer.getContext().getClassResolver(), + configuration.getKeyStoreFormat(), + configuration.getSecurityProvider(), + "file:" + configuration.getKeyStoreFile().getPath(), + "file:" + configuration.getTrustStoreFile().getPath(), + configuration.getPassphrase().toCharArray()); + } else { + sslEngineFactory = new SSLEngineFactory(); + answer = sslEngineFactory.createSSLContext(producer.getContext().getClassResolver(), + configuration.getKeyStoreFormat(), + configuration.getSecurityProvider(), + configuration.getKeyStoreResource(), + configuration.getTrustStoreResource(), + configuration.getPassphrase().toCharArray()); + } + } + + return answer; + } + + private SslHandler configureClientSSLOnDemand() throws Exception { + if (!producer.getConfiguration().isSsl()) { + return null; + } + + if (producer.getConfiguration().getSslHandler() != null) { + return producer.getConfiguration().getSslHandler(); + } else if (sslContext != null) { + SSLEngine engine = sslContext.createSSLEngine(); + engine.setUseClientMode(true); + return new SslHandler(engine); + } + + return null; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpPrincipal.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpPrincipal.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpPrincipal.java new file mode 100644 index 0000000..5e10ed9 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpPrincipal.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.netty4.http; + +import java.security.Principal; + +/** + * Http {@link Principal}. + */ +public final class HttpPrincipal implements Principal { + + private final String username; + private final String password; + + public HttpPrincipal(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public String getName() { + return username; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + @Override + public String toString() { + // do not display the password + return "HttpPrincipal[" + username + "]"; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerBootstrapFactory.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerBootstrapFactory.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerBootstrapFactory.java new file mode 100644 index 0000000..be4c9e1 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerBootstrapFactory.java @@ -0,0 +1,104 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.netty4.http; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; + +import org.apache.camel.CamelContext; +import org.apache.camel.component.netty4.NettyConsumer; +import org.apache.camel.component.netty4.NettyServerBootstrapConfiguration; +import org.apache.camel.component.netty4.SingleTCPNettyServerBootstrapFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HttpServerBootstrapFactory extends SingleTCPNettyServerBootstrapFactory { + + private static final Logger LOG = LoggerFactory.getLogger(HttpServerBootstrapFactory.class); + private final HttpServerConsumerChannelFactory channelFactory; + private int port; + private NettyServerBootstrapConfiguration bootstrapConfiguration; + private boolean compatibleCheck; + + public HttpServerBootstrapFactory(HttpServerConsumerChannelFactory channelFactory) { + this(channelFactory, true); + } + + public HttpServerBootstrapFactory(HttpServerConsumerChannelFactory channelFactory, boolean compatibleCheck) { + this.channelFactory = channelFactory; + this.compatibleCheck = compatibleCheck; + } + + @Override + public void init(CamelContext camelContext, NettyServerBootstrapConfiguration configuration, ChannelInitializer<Channel> pipelineFactory) { + super.init(camelContext, configuration, pipelineFactory); + this.port = configuration.getPort(); + this.bootstrapConfiguration = configuration; + + LOG.info("BootstrapFactory on port {} is using bootstrap configuration: [{}]", port, bootstrapConfiguration.toStringBootstrapConfiguration()); + } + + public void addConsumer(NettyConsumer consumer) { + if (compatibleCheck) { + // when adding additional consumers on the same port (eg to reuse port for multiple routes etc) then the Netty server bootstrap + // configuration must match, as its the 1st consumer that calls the init method, which configuration is used for the Netty server bootstrap + // we do this to avoid mis configuration, so people configure SSL and plain configuration on the same port etc. + + // first it may be the same instance, so only check for compatibility of different instance + if (bootstrapConfiguration != consumer.getConfiguration() && !bootstrapConfiguration.compatible(consumer.getConfiguration())) { + throw new IllegalArgumentException("Bootstrap configuration must be identical when adding additional consumer: " + consumer.getEndpoint() + " on same port: " + port + + ".\n Existing " + bootstrapConfiguration.toStringBootstrapConfiguration() + "\n New " + consumer.getConfiguration().toStringBootstrapConfiguration()); + } + } + + if (LOG.isDebugEnabled()) { + NettyHttpConsumer httpConsumer = (NettyHttpConsumer) consumer; + LOG.debug("BootstrapFactory on port {} is adding consumer with context-path {}", port, httpConsumer.getConfiguration().getPath()); + } + + channelFactory.addConsumer((NettyHttpConsumer) consumer); + } + + @Override + public void removeConsumer(NettyConsumer consumer) { + if (LOG.isDebugEnabled()) { + NettyHttpConsumer httpConsumer = (NettyHttpConsumer) consumer; + LOG.debug("BootstrapFactory on port {} is removing consumer with context-path {}", port, httpConsumer.getConfiguration().getPath()); + } + channelFactory.removeConsumer((NettyHttpConsumer) consumer); + } + + @Override + protected void doStart() throws Exception { + LOG.debug("BootstrapFactory on port {} is starting", port); + super.doStart(); + } + + @Override + public void stop() throws Exception { + // only stop if no more active consumers + int consumers = channelFactory.consumers(); + if (consumers == 0) { + LOG.debug("BootstrapFactory on port {} is stopping", port); + super.stop(); + } else { + LOG.debug("BootstrapFactory on port {} has {} registered consumers, so cannot stop yet.", port, consumers); + } + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerConsumerChannelFactory.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerConsumerChannelFactory.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerConsumerChannelFactory.java new file mode 100644 index 0000000..e1d51f6 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerConsumerChannelFactory.java @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.netty4.http; + +import io.netty.channel.ChannelHandler; + +/** + * Factory for setting up Netty {@link ChannelHandler} bound to a given Netty port. + * <p/> + * This factory allows for consumers to reuse existing {@link io.netty.bootstrap.ServerBootstrap} which + * allows to share the same port for multiple consumers. + * + * This factory is needed to ensure we can handle the situations when consumers is added and removing in + * a dynamic environment such as OSGi, where Camel applications can be hot-deployed. And we want these + * Camel applications to be able to share the same Netty port in a easy way. + */ +public interface HttpServerConsumerChannelFactory { + + /** + * Initializes this consumer channel factory with the given port. + */ + void init(int port); + + /** + * The port number this consumer channel factory is using. + */ + int getPort(); + + /** + * Adds the given consumer. + */ + void addConsumer(NettyHttpConsumer consumer); + + /** + * Removes the given consumer + */ + void removeConsumer(NettyHttpConsumer consumer); + + /** + * Number of active consumers + */ + int consumers(); + + /** + * Gets the {@link ChannelHandler} + */ + ChannelHandler getChannelHandler(); + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerPipelineFactory.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerPipelineFactory.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerPipelineFactory.java new file mode 100644 index 0000000..51ae265 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerPipelineFactory.java @@ -0,0 +1,172 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.netty4.http; + + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.http.HttpContentCompressor; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import io.netty.handler.ssl.SslHandler; +import io.netty.util.concurrent.EventExecutorGroup; +import org.apache.camel.CamelContext; +import org.apache.camel.component.netty4.NettyConsumer; +import org.apache.camel.component.netty4.NettyServerBootstrapConfiguration; +import org.apache.camel.component.netty4.ServerPipelineFactory; +import org.apache.camel.component.netty4.ssl.SSLEngineFactory; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link ServerPipelineFactory} for the Netty HTTP server. + */ +public class HttpServerPipelineFactory extends ServerPipelineFactory { + + private static final Logger LOG = LoggerFactory.getLogger(HttpServerPipelineFactory.class); + protected NettyHttpConsumer consumer; + protected SSLContext sslContext; + protected NettyHttpConfiguration configuration; + + public HttpServerPipelineFactory() { + // default constructor needed + } + + public HttpServerPipelineFactory(NettyHttpConsumer nettyConsumer) { + this.consumer = nettyConsumer; + this.configuration = nettyConsumer.getConfiguration(); + try { + this.sslContext = createSSLContext(consumer.getContext(), consumer.getConfiguration()); + } catch (Exception e) { + throw ObjectHelper.wrapRuntimeCamelException(e); + } + + if (sslContext != null) { + LOG.info("Created SslContext {}", sslContext); + } + } + + @Override + public ServerPipelineFactory createPipelineFactory(NettyConsumer nettyConsumer) { + return new HttpServerPipelineFactory((NettyHttpConsumer) nettyConsumer); + } + + @Override + protected void initChannel(Channel ch) throws Exception { + // create a new pipeline + ChannelPipeline pipeline = ch.pipeline(); + + SslHandler sslHandler = configureServerSSLOnDemand(); + if (sslHandler != null) { + //TODO must close on SSL exception + // sslHandler.setCloseOnSSLException(true); + LOG.debug("Server SSL handler configured and added as an interceptor against the ChannelPipeline: {}", sslHandler); + pipeline.addLast("ssl", sslHandler); + } + + pipeline.addLast("decoder", new HttpRequestDecoder()); + pipeline.addLast("aggregator", new HttpObjectAggregator(configuration.getChunkedMaxContentLength())); + + pipeline.addLast("encoder", new HttpResponseEncoder()); + if (supportCompressed()) { + pipeline.addLast("deflater", new HttpContentCompressor()); + } + + int port = consumer.getConfiguration().getPort(); + ChannelHandler handler = consumer.getEndpoint().getComponent().getMultiplexChannelHandler(port).getChannelHandler(); + + if (consumer.getConfiguration().isOrderedThreadPoolExecutor()) { + EventExecutorGroup applicationExecutor = consumer.getEndpoint().getComponent().getExecutorService(); + pipeline.addLast(applicationExecutor, "handler", handler); + } else { + pipeline.addLast("handler", handler); + } + + } + + private SSLContext createSSLContext(CamelContext camelContext, NettyServerBootstrapConfiguration configuration) throws Exception { + if (!configuration.isSsl()) { + return null; + } + + SSLContext answer; + + // create ssl context once + if (configuration.getSslContextParameters() != null) { + answer = configuration.getSslContextParameters().createSSLContext(); + } else { + if (configuration.getKeyStoreFile() == null && configuration.getKeyStoreResource() == null) { + LOG.debug("keystorefile is null"); + } + if (configuration.getTrustStoreFile() == null && configuration.getTrustStoreResource() == null) { + LOG.debug("truststorefile is null"); + } + if (configuration.getPassphrase().toCharArray() == null) { + LOG.debug("passphrase is null"); + } + + SSLEngineFactory sslEngineFactory; + if (configuration.getKeyStoreFile() != null || configuration.getTrustStoreFile() != null) { + sslEngineFactory = new SSLEngineFactory(); + answer = sslEngineFactory.createSSLContext(camelContext.getClassResolver(), + configuration.getKeyStoreFormat(), + configuration.getSecurityProvider(), + "file:" + configuration.getKeyStoreFile().getPath(), + "file:" + configuration.getTrustStoreFile().getPath(), + configuration.getPassphrase().toCharArray()); + } else { + sslEngineFactory = new SSLEngineFactory(); + answer = sslEngineFactory.createSSLContext(camelContext.getClassResolver(), + configuration.getKeyStoreFormat(), + configuration.getSecurityProvider(), + configuration.getKeyStoreResource(), + configuration.getTrustStoreResource(), + configuration.getPassphrase().toCharArray()); + } + } + + return answer; + } + + private SslHandler configureServerSSLOnDemand() throws Exception { + if (!consumer.getConfiguration().isSsl()) { + return null; + } + + if (consumer.getConfiguration().getSslHandler() != null) { + return consumer.getConfiguration().getSslHandler(); + } else if (sslContext != null) { + SSLEngine engine = sslContext.createSSLEngine(); + engine.setUseClientMode(false); + engine.setNeedClientAuth(consumer.getConfiguration().isNeedClientAuth()); + return new SslHandler(engine); + } + + return null; + } + + private boolean supportCompressed() { + return consumer.getEndpoint().getConfiguration().isCompression(); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerSharedPipelineFactory.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerSharedPipelineFactory.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerSharedPipelineFactory.java new file mode 100644 index 0000000..3f47ff9 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/HttpServerSharedPipelineFactory.java @@ -0,0 +1,159 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.netty4.http; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.http.HttpContentCompressor; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import io.netty.handler.ssl.SslHandler; +import org.apache.camel.component.netty4.NettyConsumer; +import org.apache.camel.component.netty4.ServerPipelineFactory; +import org.apache.camel.component.netty4.ssl.SSLEngineFactory; +import org.apache.camel.impl.DefaultClassResolver; +import org.apache.camel.spi.ClassResolver; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A shared {@link org.apache.camel.component.netty.ServerPipelineFactory} for a shared Netty HTTP server. + * + * @see NettySharedHttpServer + */ +public class HttpServerSharedPipelineFactory extends HttpServerPipelineFactory { + + private static final Logger LOG = LoggerFactory.getLogger(HttpServerSharedPipelineFactory.class); + private final NettySharedHttpServerBootstrapConfiguration configuration; + private final HttpServerConsumerChannelFactory channelFactory; + private final ClassResolver classResolver; + private SSLContext sslContext; + + public HttpServerSharedPipelineFactory(NettySharedHttpServerBootstrapConfiguration configuration, HttpServerConsumerChannelFactory channelFactory, + ClassResolver classResolver) { + this.configuration = configuration; + this.channelFactory = channelFactory; + // fallback and use default resolver + this.classResolver = classResolver != null ? classResolver : new DefaultClassResolver(); + + try { + this.sslContext = createSSLContext(); + } catch (Exception e) { + throw ObjectHelper.wrapRuntimeCamelException(e); + } + + if (sslContext != null) { + LOG.info("Created SslContext {}", sslContext); + } + } + + @Override + public ServerPipelineFactory createPipelineFactory(NettyConsumer nettyConsumer) { + throw new UnsupportedOperationException("Should not call this operation"); + } + + @Override + protected void initChannel(Channel ch) throws Exception { + // create a new pipeline + ChannelPipeline pipeline = ch.pipeline(); + + SslHandler sslHandler = configureServerSSLOnDemand(); + if (sslHandler != null) { + LOG.debug("Server SSL handler configured and added as an interceptor against the ChannelPipeline: {}", sslHandler); + pipeline.addLast("ssl", sslHandler); + } + + pipeline.addLast("decoder", new HttpRequestDecoder()); + if (configuration.isChunked()) { + pipeline.addLast("aggregator", new HttpObjectAggregator(configuration.getChunkedMaxContentLength())); + } + pipeline.addLast("encoder", new HttpResponseEncoder()); + if (configuration.isCompression()) { + pipeline.addLast("deflater", new HttpContentCompressor()); + } + + pipeline.addLast("handler", channelFactory.getChannelHandler()); + + } + + private SSLContext createSSLContext() throws Exception { + if (!configuration.isSsl()) { + return null; + } + + SSLContext answer; + + // create ssl context once + if (configuration.getSslContextParameters() != null) { + answer = configuration.getSslContextParameters().createSSLContext(); + } else { + if (configuration.getKeyStoreFile() == null && configuration.getKeyStoreResource() == null) { + LOG.debug("keystorefile is null"); + } + if (configuration.getTrustStoreFile() == null && configuration.getTrustStoreResource() == null) { + LOG.debug("truststorefile is null"); + } + if (configuration.getPassphrase().toCharArray() == null) { + LOG.debug("passphrase is null"); + } + + SSLEngineFactory sslEngineFactory; + if (configuration.getKeyStoreFile() != null || configuration.getTrustStoreFile() != null) { + sslEngineFactory = new SSLEngineFactory(); + answer = sslEngineFactory.createSSLContext(classResolver, + configuration.getKeyStoreFormat(), + configuration.getSecurityProvider(), + "file:" + configuration.getKeyStoreFile().getPath(), + "file:" + configuration.getTrustStoreFile().getPath(), + configuration.getPassphrase().toCharArray()); + } else { + sslEngineFactory = new SSLEngineFactory(); + answer = sslEngineFactory.createSSLContext(classResolver, + configuration.getKeyStoreFormat(), + configuration.getSecurityProvider(), + configuration.getKeyStoreResource(), + configuration.getTrustStoreResource(), + configuration.getPassphrase().toCharArray()); + } + } + + return answer; + } + + private SslHandler configureServerSSLOnDemand() throws Exception { + if (!configuration.isSsl()) { + return null; + } + + if (configuration.getSslHandler() != null) { + return configuration.getSslHandler(); + } else if (sslContext != null) { + SSLEngine engine = sslContext.createSSLEngine(); + engine.setUseClientMode(false); + engine.setNeedClientAuth(configuration.isNeedClientAuth()); + return new SslHandler(engine); + } + + return null; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/JAASSecurityAuthenticator.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/JAASSecurityAuthenticator.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/JAASSecurityAuthenticator.java new file mode 100644 index 0000000..4f21def --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/JAASSecurityAuthenticator.java @@ -0,0 +1,72 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.netty4.http; + +import java.security.Principal; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A JAAS based {@link SecurityAuthenticator} implementation. + */ +public class JAASSecurityAuthenticator extends SecurityAuthenticatorSupport { + + private static final Logger LOG = LoggerFactory.getLogger(JAASSecurityAuthenticator.class); + + @Override + public Subject login(HttpPrincipal principal) throws LoginException { + if (ObjectHelper.isEmpty(getName())) { + throw new IllegalArgumentException("Realm has not been configured on this SecurityAuthenticator: " + this); + } + + LOG.trace("Login username: {} using realm: {}", principal.getName(), getName()); + LoginContext context = new LoginContext(getName(), new HttpPrincipalCallbackHandler(principal)); + context.login(); + Subject subject = context.getSubject(); + LOG.debug("Login username: {} successful returning Subject: {}", principal.getName(), subject); + + if (LOG.isTraceEnabled()) { + for (Principal p : subject.getPrincipals()) { + LOG.trace("Principal on subject {} -> {}", p.getClass().getName(), p.getName()); + } + } + + return subject; + } + + @Override + public void logout(Subject subject) throws LoginException { + if (ObjectHelper.isEmpty(getName())) { + throw new LoginException("Realm has not been configured on this SecurityAuthenticator: " + this); + } + + String username = ""; + if (!subject.getPrincipals().isEmpty()) { + username = subject.getPrincipals().iterator().next().getName(); + } + LOG.trace("Logging out username: {} using realm: {}", username, getName()); + LoginContext context = new LoginContext(getName(), subject); + context.logout(); + LOG.debug("Logout username: {} successful", username); + } + +}