http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/RestNettyHttpBinding.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/RestNettyHttpBinding.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/RestNettyHttpBinding.java new file mode 100644 index 0000000..597b6d5 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/RestNettyHttpBinding.java @@ -0,0 +1,92 @@ +/** + * 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.net.URI; +import java.util.Map; + +import io.netty.handler.codec.http.FullHttpRequest; +import org.apache.camel.Exchange; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.spi.HeaderFilterStrategy; + + +/** + * A {@link org.apache.camel.component.netty.http.NettyHttpBinding} that supports the Rest DSL. + */ +public class RestNettyHttpBinding extends DefaultNettyHttpBinding { + + public RestNettyHttpBinding() { + } + + public RestNettyHttpBinding(HeaderFilterStrategy headerFilterStrategy) { + super(headerFilterStrategy); + } + + public RestNettyHttpBinding copy() { + try { + return (RestNettyHttpBinding)this.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeCamelException(e); + } + } + + @Override + public void populateCamelHeaders(FullHttpRequest request, Map<String, Object> headers, Exchange exchange, NettyHttpConfiguration configuration) throws Exception { + super.populateCamelHeaders(request, headers, exchange, configuration); + + String path = request.getUri(); + if (path == null) { + return; + } + + // skip the scheme/host/port etc, as we only want the context-path + URI uri = new URI(path); + path = uri.getPath(); + + // in the endpoint the user may have defined rest {} placeholders + // so we need to map those placeholders with data from the incoming request context path + + String consumerPath = configuration.getPath(); + + if (useRestMatching(consumerPath)) { + + // split using single char / is optimized in the jdk + String[] paths = path.split("/"); + String[] consumerPaths = consumerPath.split("/"); + + for (int i = 0; i < consumerPaths.length; i++) { + if (paths.length < i) { + break; + } + String p1 = consumerPaths[i]; + if (p1.startsWith("{") && p1.endsWith("}")) { + String key = p1.substring(1, p1.length() - 1); + String value = paths[i]; + if (value != null) { + NettyHttpHelper.appendHeader(headers, key, value); + } + } + } + } + } + + private boolean useRestMatching(String path) { + // only need to do rest matching if using { } placeholders + return path.indexOf('{') > -1; + } +}
http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityAuthenticator.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityAuthenticator.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityAuthenticator.java new file mode 100644 index 0000000..8d99f11 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityAuthenticator.java @@ -0,0 +1,76 @@ +/** + * 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.security.auth.Subject; +import javax.security.auth.login.LoginException; + +/** + * A {@link SecurityAuthenticator} allows to plugin custom authenticators, + * such as JAAS based or custom implementations. + */ +public interface SecurityAuthenticator { + + /** + * Sets the name of the realm to use. + */ + void setName(String name); + + /** + * Gets the name of the realm. + */ + String getName(); + + /** + * Sets the role class names (separated by comma) + * <p/> + * By default if no explicit role class names has been configured, then this implementation + * will assume the {@link Subject} {@link java.security.Principal}s is a role if the classname + * contains the word <tt>role</tt> (lower cased). + * + * @param names a list of FQN class names for role {@link java.security.Principal} implementations. + */ + void setRoleClassNames(String names); + + /** + * Attempts to login the {@link java.security.Principal} on this realm. + * <p/> + * The login is a success if no Exception is thrown, and a {@link Subject} is returned. + * + * @param principal the principal + * @return the subject for the logged in principal, must <b>not</b> be <tt>null</tt> + * @throws LoginException is thrown if error logging in the {@link java.security.Principal} + */ + Subject login(HttpPrincipal principal) throws LoginException; + + /** + * Attempt to logout the subject. + * + * @param subject subject to logout + * @throws LoginException is thrown if error logging out subject + */ + void logout(Subject subject) throws LoginException; + + /** + * Gets the user roles from the given {@link Subject} + * + * @param subject the subject + * @return <tt>null</tt> if no roles, otherwise a String with roles separated by comma. + */ + String getUserRoles(Subject subject); + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityAuthenticatorSupport.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityAuthenticatorSupport.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityAuthenticatorSupport.java new file mode 100644 index 0000000..f4622b4 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityAuthenticatorSupport.java @@ -0,0 +1,127 @@ +/** + * 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.IOException; +import java.security.Principal; +import java.util.Iterator; +import java.util.Locale; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A base class for {@link SecurityAuthenticator}. + */ +public abstract class SecurityAuthenticatorSupport implements SecurityAuthenticator { + + private String name; + private String roleClassNames; + + public SecurityAuthenticatorSupport() { + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setRoleClassNames(String roleClassNames) { + this.roleClassNames = roleClassNames; + } + + /** + * Is the given principal a role class? + * + * @param principal the principal + * @return <tt>true</tt> if role class, <tt>false</tt> if not + */ + protected boolean isRoleClass(Principal principal) { + if (roleClassNames == null) { + // by default assume its a role when the classname has role in its name + return principal.getClass().getName().toLowerCase(Locale.US).contains("role"); + } + + // check each role class name if they match the principal class name + Iterator<Object> it = ObjectHelper.createIterator(roleClassNames); + while (it.hasNext()) { + String name = it.next().toString().trim(); + if (principal.getClass().getName().equals(name)) { + return true; + } + } + return false; + } + + @Override + public String getUserRoles(Subject subject) { + StringBuilder sb = new StringBuilder(); + for (Principal p : subject.getPrincipals()) { + if (isRoleClass(p)) { + if (sb.length() > 0) { + sb.append(","); + } + sb.append(p.getName()); + } + } + if (sb.length() > 0) { + return sb.toString(); + } else { + return null; + } + } + + /** + * {@link javax.security.auth.callback.CallbackHandler} that provides the username and password. + */ + public static final class HttpPrincipalCallbackHandler implements CallbackHandler { + + private static final Logger LOG = LoggerFactory.getLogger(HttpPrincipalCallbackHandler.class); + + private final HttpPrincipal principal; + + public HttpPrincipalCallbackHandler(HttpPrincipal principal) { + this.principal = principal; + } + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) { + LOG.trace("Callback {}", callback); + if (callback instanceof PasswordCallback) { + PasswordCallback pc = (PasswordCallback) callback; + LOG.trace("Setting password on callback {}", pc); + pc.setPassword(principal.getPassword().toCharArray()); + } else if (callback instanceof NameCallback) { + NameCallback nc = (NameCallback) callback; + LOG.trace("Setting username on callback {}", nc); + nc.setName(principal.getName()); + } + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityConstraint.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityConstraint.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityConstraint.java new file mode 100644 index 0000000..8d56502 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityConstraint.java @@ -0,0 +1,31 @@ +/** + * 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; + +public interface SecurityConstraint { + + /** + * Performs a security restricted check for the given web resource. + * <p/> + * The returned value indicates which roles the user must be in to access the restricted resource. + * + * @param url the web resource + * @return <tt>null</tt> if not restricted, otherwise <tt>*</tt> (wildcard) matches any roles, otherwise a comma separated String with roles + */ + String restricted(String url); + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityConstraintMapping.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityConstraintMapping.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityConstraintMapping.java new file mode 100644 index 0000000..78cbac3 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/SecurityConstraintMapping.java @@ -0,0 +1,133 @@ +/** + * 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.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.camel.util.EndpointHelper; + +/** + * A default {@link SecurityConstraint} which can be used to define a set of mappings to + * as constraints. + * <p/> + * This constraint will match as <tt>true</tt> if no inclusions has been defined. + * First all the inclusions is check for matching. If a inclusion matches, + * then the exclusion is checked, and if any of them matches, then the exclusion + * will override the match and force returning <tt>false</tt>. + * <p/> + * Wildcards and regular expressions is supported as this implementation uses + * {@link EndpointHelper#matchPattern(String, String)} method for matching. + * <p/> + * This restricted constraint allows you to setup context path rules that will restrict + * access to paths, and then override and have exclusions that may allow access to + * public paths. + */ +public class SecurityConstraintMapping implements SecurityConstraint { + + // url -> roles + private Map<String, String> inclusions; + // url + private Set<String> exclusions; + + @Override + public String restricted(String url) { + // check excluded first + if (excludedUrl(url)) { + return null; + } + + // is the url restricted? + String constraint = includedUrl(url); + if (constraint == null) { + return null; + } + + // is there any roles for the restricted url? + String roles = inclusions != null ? inclusions.get(constraint) : null; + if (roles == null) { + // use wildcard to indicate any role is accepted + return "*"; + } else { + return roles; + } + } + + private String includedUrl(String url) { + String candidate = null; + if (inclusions != null && !inclusions.isEmpty()) { + for (String constraint : inclusions.keySet()) { + if (EndpointHelper.matchPattern(url, constraint)) { + if (candidate == null) { + candidate = constraint; + } else if (constraint.length() > candidate.length()) { + // we want the constraint that has the longest context-path matching as its + // the most explicit for the target url + candidate = constraint; + } + } + } + return candidate; + } + + // by default if no included has been configured then everything is restricted + return "*"; + } + + private boolean excludedUrl(String url) { + if (exclusions != null && !exclusions.isEmpty()) { + for (String constraint : exclusions) { + if (EndpointHelper.matchPattern(url, constraint)) { + // force not matches if this was an exclusion + return true; + } + } + } + + return false; + } + + public void addInclusion(String constraint) { + if (inclusions == null) { + inclusions = new java.util.LinkedHashMap<String, String>(); + } + inclusions.put(constraint, null); + } + + public void addInclusion(String constraint, String roles) { + if (inclusions == null) { + inclusions = new java.util.LinkedHashMap<String, String>(); + } + inclusions.put(constraint, roles); + } + + public void addExclusion(String constraint) { + if (exclusions == null) { + exclusions = new LinkedHashSet<String>(); + } + exclusions.add(constraint); + } + + public void setInclusions(Map<String, String> inclusions) { + this.inclusions = inclusions; + } + + public void setExclusions(Set<String> exclusions) { + this.exclusions = exclusions; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/handlers/HttpClientChannelHandler.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/handlers/HttpClientChannelHandler.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/handlers/HttpClientChannelHandler.java new file mode 100644 index 0000000..f2bc20c --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/handlers/HttpClientChannelHandler.java @@ -0,0 +1,46 @@ +/** + * 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.handlers; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpResponse; + +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.component.netty4.handlers.ClientChannelHandler; +import org.apache.camel.component.netty4.http.NettyHttpProducer; + +/** + * Netty HTTP {@link org.apache.camel.component.netty.handlers.ClientChannelHandler} that handles the response combing + * back from the HTTP server, called by this client. + * + */ +public class HttpClientChannelHandler extends ClientChannelHandler { + private final NettyHttpProducer producer; + + public HttpClientChannelHandler(NettyHttpProducer producer) { + super(producer); + this.producer = producer; + } + + @Override + protected Message getResponseMessage(Exchange exchange, ChannelHandlerContext ctx, Object message) throws Exception { + FullHttpResponse response = (FullHttpResponse) message; + // use the binding + return producer.getEndpoint().getNettyHttpBinding().toCamelMessage(response, exchange, producer.getConfiguration()); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/handlers/HttpServerChannelHandler.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/handlers/HttpServerChannelHandler.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/handlers/HttpServerChannelHandler.java new file mode 100644 index 0000000..38e0798 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/handlers/HttpServerChannelHandler.java @@ -0,0 +1,316 @@ +/** + * 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.handlers; + +import java.net.SocketAddress; +import java.net.URI; +import java.nio.channels.ClosedChannelException; +import java.nio.charset.Charset; +import java.util.Iterator; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginException; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.base64.Base64; +import io.netty.handler.codec.http.DefaultHttpResponse; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import org.apache.camel.Exchange; +import org.apache.camel.LoggingLevel; +import org.apache.camel.component.netty4.NettyConsumer; +import org.apache.camel.component.netty4.NettyConverter; +import org.apache.camel.component.netty4.NettyHelper; +import org.apache.camel.component.netty4.handlers.ServerChannelHandler; +import org.apache.camel.component.netty4.http.HttpPrincipal; +import org.apache.camel.component.netty4.http.NettyHttpConsumer; +import org.apache.camel.component.netty4.http.NettyHttpSecurityConfiguration; +import org.apache.camel.component.netty4.http.SecurityAuthenticator; +import org.apache.camel.util.CamelLogger; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive; +import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; +import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpResponseStatus.SERVICE_UNAVAILABLE; +import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +/** + * Netty HTTP {@link ServerChannelHandler} that handles the incoming HTTP requests and routes + * the received message in Camel. + */ +public class HttpServerChannelHandler extends ServerChannelHandler { + + // use NettyHttpConsumer as logger to make it easier to read the logs as this is part of the consumer + private static final Logger LOG = LoggerFactory.getLogger(NettyHttpConsumer.class); + private final NettyHttpConsumer consumer; + private HttpRequest request; + + public HttpServerChannelHandler(NettyHttpConsumer consumer) { + super(consumer); + this.consumer = consumer; + } + + public NettyHttpConsumer getConsumer() { + return consumer; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { + // store request, as this channel handler is created per pipeline + request = (HttpRequest) msg; + + LOG.debug("Message received: {}", request); + + if (consumer.isSuspended()) { + // are we suspended? + LOG.debug("Consumer suspended, cannot service request {}", request); + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, SERVICE_UNAVAILABLE); + response.headers().set(Exchange.CONTENT_TYPE, "text/plain"); + response.headers().set(Exchange.CONTENT_LENGTH, 0); + ctx.writeAndFlush(response); + return; + } + + // if its an OPTIONS request then return which methods is allowed + if ("OPTIONS".equals(request.getMethod().name())) { + String s; + if (consumer.getEndpoint().getHttpMethodRestrict() != null) { + s = "OPTIONS," + consumer.getEndpoint().getHttpMethodRestrict(); + } else { + // allow them all + s = "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATCH"; + } + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); + response.headers().set("Allow", s); + response.headers().set(Exchange.CONTENT_TYPE, "text/plain"); + response.headers().set(Exchange.CONTENT_LENGTH, 0); + ctx.writeAndFlush(response); + return; + } + if (consumer.getEndpoint().getHttpMethodRestrict() != null + && !consumer.getEndpoint().getHttpMethodRestrict().contains(request.getMethod().name())) { + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, METHOD_NOT_ALLOWED); + response.headers().set(Exchange.CONTENT_TYPE, "text/plain"); + response.headers().set(Exchange.CONTENT_LENGTH, 0); + ctx.writeAndFlush(response); + return; + } + if ("TRACE".equals(request.getMethod().name()) && !consumer.getEndpoint().isTraceEnabled()) { + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, METHOD_NOT_ALLOWED); + response.headers().set(Exchange.CONTENT_TYPE, "text/plain"); + response.headers().set(Exchange.CONTENT_LENGTH, 0); + ctx.writeAndFlush(response); + return; + } + // must include HOST header as required by HTTP 1.1 + if (!request.headers().names().contains(HttpHeaders.Names.HOST)) { + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, BAD_REQUEST); + //response.setChunked(false); + response.headers().set(Exchange.CONTENT_TYPE, "text/plain"); + response.headers().set(Exchange.CONTENT_LENGTH, 0); + ctx.writeAndFlush(response); + return; + } + + // is basic auth configured + NettyHttpSecurityConfiguration security = consumer.getEndpoint().getSecurityConfiguration(); + if (security != null && security.isAuthenticate() && "Basic".equalsIgnoreCase(security.getConstraint())) { + String url = request.getUri(); + + // drop parameters from url + if (url.contains("?")) { + url = ObjectHelper.before(url, "?"); + } + + // we need the relative path without the hostname and port + URI uri = new URI(request.getUri()); + String target = uri.getPath(); + + // strip the starting endpoint path so the target is relative to the endpoint uri + String path = consumer.getConfiguration().getPath(); + if (path != null && target.startsWith(path)) { + target = target.substring(path.length()); + } + + // is it a restricted resource? + String roles; + if (security.getSecurityConstraint() != null) { + // if restricted returns null, then the resource is not restricted and we should not authenticate the user + roles = security.getSecurityConstraint().restricted(target); + } else { + // assume any roles is valid if no security constraint has been configured + roles = "*"; + } + if (roles != null) { + // basic auth subject + HttpPrincipal principal = extractBasicAuthSubject(request); + + // authenticate principal and check if the user is in role + Subject subject = null; + boolean inRole = true; + if (principal != null) { + subject = authenticate(security.getSecurityAuthenticator(), security.getLoginDeniedLoggingLevel(), principal); + if (subject != null) { + String userRoles = security.getSecurityAuthenticator().getUserRoles(subject); + inRole = matchesRoles(roles, userRoles); + } + } + + if (principal == null || subject == null || !inRole) { + if (principal == null) { + LOG.debug("Http Basic Auth required for resource: {}", url); + } else if (subject == null) { + LOG.debug("Http Basic Auth not authorized for username: {}", principal.getUsername()); + } else { + LOG.debug("Http Basic Auth not in role for username: {}", principal.getUsername()); + } + // restricted resource, so send back 401 to require valid username/password + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, UNAUTHORIZED); + response.headers().set("WWW-Authenticate", "Basic realm=\"" + security.getRealm() + "\""); + response.headers().set(Exchange.CONTENT_TYPE, "text/plain"); + response.headers().set(Exchange.CONTENT_LENGTH, 0); + ctx.writeAndFlush(response); + return; + } else { + LOG.debug("Http Basic Auth authorized for username: {}", principal.getUsername()); + } + } + } + + // let Camel process this message + super.channelRead0(ctx, msg); + } + + protected boolean matchesRoles(String roles, String userRoles) { + // matches if no role restrictions or any role is accepted + if (roles.equals("*")) { + return true; + } + + // see if any of the user roles is contained in the roles list + Iterator<Object> it = ObjectHelper.createIterator(userRoles); + while (it.hasNext()) { + String userRole = it.next().toString(); + if (roles.contains(userRole)) { + return true; + } + } + + return false; + } + + /** + * Extracts the username and password details from the HTTP basic header Authorization. + * <p/> + * This requires that the <tt>Authorization</tt> HTTP header is provided, and its using Basic. + * Currently Digest is <b>not</b> supported. + * + * @return {@link HttpPrincipal} with username and password details, or <tt>null</tt> if not possible to extract + */ + protected static HttpPrincipal extractBasicAuthSubject(HttpRequest request) { + String auth = request.headers().get("Authorization"); + if (auth != null) { + String constraint = ObjectHelper.before(auth, " "); + if (constraint != null) { + if ("Basic".equalsIgnoreCase(constraint.trim())) { + String decoded = ObjectHelper.after(auth, " "); + // the decoded part is base64 encoded, so we need to decode that + ByteBuf buf = NettyConverter.toByteBuffer(decoded.getBytes()); + ByteBuf out = Base64.decode(buf); + String userAndPw = out.toString(Charset.defaultCharset()); + String username = ObjectHelper.before(userAndPw, ":"); + String password = ObjectHelper.after(userAndPw, ":"); + HttpPrincipal principal = new HttpPrincipal(username, password); + + LOG.debug("Extracted Basic Auth principal from HTTP header: {}", principal); + return principal; + } + } + } + return null; + } + + /** + * Authenticates the http basic auth subject. + * + * @param authenticator the authenticator + * @param principal the principal + * @return <tt>true</tt> if username and password is valid, <tt>false</tt> if not + */ + protected Subject authenticate(SecurityAuthenticator authenticator, LoggingLevel deniedLoggingLevel, HttpPrincipal principal) { + try { + return authenticator.login(principal); + } catch (LoginException e) { + CamelLogger logger = new CamelLogger(LOG, deniedLoggingLevel); + logger.log("Cannot login " + principal.getName() + " due " + e.getMessage(), e); + } + return null; + } + + @Override + protected void beforeProcess(Exchange exchange, final ChannelHandlerContext ctx, final Object message) { + if (consumer.getConfiguration().isBridgeEndpoint()) { + exchange.setProperty(Exchange.SKIP_GZIP_ENCODING, Boolean.TRUE); + exchange.setProperty(Exchange.SKIP_WWW_FORM_URLENCODED, Boolean.TRUE); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + + // only close if we are still allowed to run + if (consumer.isRunAllowed()) { + + if (cause instanceof ClosedChannelException) { + LOG.debug("Channel already closed. Ignoring this exception."); + } else { + LOG.warn("Closing channel as an exception was thrown from Netty", cause); + // close channel in case an exception was thrown + NettyHelper.close(ctx.channel()); + } + } + } + + @Override + protected ChannelFutureListener createResponseFutureListener(NettyConsumer consumer, Exchange exchange, SocketAddress remoteAddress) { + // make sure to close channel if not keep-alive + if (request != null && isKeepAlive(request)) { + LOG.trace("Request has Connection: keep-alive so Channel is not being closed"); + return null; + } else { + LOG.trace("Request is not Connection: close so Channel is being closed"); + return ChannelFutureListener.CLOSE; + } + } + + @Override + protected Object getResponseBody(Exchange exchange) throws Exception { + // use the binding + if (exchange.hasOut()) { + return consumer.getEndpoint().getNettyHttpBinding().toNettyResponse(exchange.getOut(), consumer.getConfiguration()); + } else { + return consumer.getEndpoint().getNettyHttpBinding().toNettyResponse(exchange.getIn(), consumer.getConfiguration()); + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/handlers/HttpServerMultiplexChannelHandler.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/handlers/HttpServerMultiplexChannelHandler.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/handlers/HttpServerMultiplexChannelHandler.java new file mode 100644 index 0000000..374a846 --- /dev/null +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/handlers/HttpServerMultiplexChannelHandler.java @@ -0,0 +1,235 @@ +/** + * 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.handlers; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.DefaultHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import org.apache.camel.Exchange; +import org.apache.camel.component.netty4.http.ContextPathMatcher; +import org.apache.camel.component.netty4.http.HttpServerConsumerChannelFactory; +import org.apache.camel.component.netty4.http.NettyHttpConsumer; +import org.apache.camel.component.netty4.http.RestContextPathMatcher; +import org.apache.camel.util.UnsafeUriCharactersEncoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +/** + * A multiplex {@link org.apache.camel.component.netty4.http.HttpServerPipelineFactory} which keeps a list of handlers, and delegates to the + * target handler based on the http context path in the incoming request. This is used to allow to reuse + * the same Netty consumer, allowing to have multiple routes on the same netty {@link io.netty.bootstrap.ServerBootstrap} + */ +@Sharable +public class HttpServerMultiplexChannelHandler extends SimpleChannelInboundHandler<Object> implements HttpServerConsumerChannelFactory { + + // use NettyHttpConsumer as logger to make it easier to read the logs as this is part of the consumer + private static final Logger LOG = LoggerFactory.getLogger(NettyHttpConsumer.class); + private static final AttributeKey<HttpServerChannelHandler> SERVER_HANDLER_KEY = AttributeKey.valueOf("serverHandler"); + private final ConcurrentMap<ContextPathMatcher, HttpServerChannelHandler> consumers = new ConcurrentHashMap<ContextPathMatcher, HttpServerChannelHandler>(); + private int port; + private String token; + private int len; + + public HttpServerMultiplexChannelHandler() { + // must have default no-arg constructor to allow IoC containers to manage it + } + + public void init(int port) { + this.port = port; + this.token = ":" + port; + this.len = token.length(); + } + + public void addConsumer(NettyHttpConsumer consumer) { + String rawPath = consumer.getConfiguration().getPath(); + String path = pathAsKey(consumer.getConfiguration().getPath()); + // use rest path matcher in case Rest DSL is in use + ContextPathMatcher matcher = new RestContextPathMatcher(rawPath, path, consumer.getConfiguration().isMatchOnUriPrefix()); + consumers.put(matcher, new HttpServerChannelHandler(consumer)); + } + + public void removeConsumer(NettyHttpConsumer consumer) { + String rawPath = consumer.getConfiguration().getPath(); + String path = pathAsKey(consumer.getConfiguration().getPath()); + // use rest path matcher in case Rest DSL is in use + ContextPathMatcher matcher = new RestContextPathMatcher(rawPath, path, consumer.getConfiguration().isMatchOnUriPrefix()); + consumers.remove(matcher); + } + + public int consumers() { + return consumers.size(); + } + + public int getPort() { + return port; + } + + public ChannelHandler getChannelHandler() { + return this; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { + // store request, as this channel handler is created per pipeline + HttpRequest request = (HttpRequest) msg; + + LOG.debug("Message received: {}", request); + + HttpServerChannelHandler handler = getHandler(request); + if (handler != null) { + Attribute<HttpServerChannelHandler> attr = ctx.attr(SERVER_HANDLER_KEY); + // store handler as attachment + attr.set(handler); + if (msg instanceof HttpContent) { + // need to hold the reference of content + HttpContent httpContent = (HttpContent) msg; + httpContent.content().retain(); + } + handler.channelRead(ctx, request); + } else { + // this resource is not found, so send empty response back + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND); + response.headers().set(Exchange.CONTENT_TYPE, "text/plain"); + response.headers().set(Exchange.CONTENT_LENGTH, 0); + ctx.writeAndFlush(response); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + Attribute<HttpServerChannelHandler> attr = ctx.attr(SERVER_HANDLER_KEY); + HttpServerChannelHandler handler = attr.get(); + if (handler != null) { + handler.exceptionCaught(ctx, cause); + } else { + // we cannot throw the exception here + LOG.warn("HttpServerChannelHandler is not found as attachment to handle exception, send 404 back to the client.", cause); + // Now we just send 404 back to the client + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND); + response.headers().set(Exchange.CONTENT_TYPE, "text/plain"); + response.headers().set(Exchange.CONTENT_LENGTH, 0); + ctx.writeAndFlush(response); + } + } + + private HttpServerChannelHandler getHandler(HttpRequest request) { + HttpServerChannelHandler answer = null; + + // need to strip out host and port etc, as we only need the context-path for matching + String method = request.getMethod().name(); + if (method == null) { + return null; + } + + String path = request.getUri(); + int idx = path.indexOf(token); + if (idx > -1) { + path = path.substring(idx + len); + } + // use the path as key to find the consumer handler to use + path = pathAsKey(path); + + + List<Map.Entry<ContextPathMatcher, HttpServerChannelHandler>> candidates = new ArrayList<Map.Entry<ContextPathMatcher, HttpServerChannelHandler>>(); + + // first match by http method + for (Map.Entry<ContextPathMatcher, HttpServerChannelHandler> entry : consumers.entrySet()) { + NettyHttpConsumer consumer = entry.getValue().getConsumer(); + String restrict = consumer.getEndpoint().getHttpMethodRestrict(); + if (entry.getKey().matchMethod(method, restrict)) { + candidates.add(entry); + } + } + + // then see if we got a direct match + Iterator<Map.Entry<ContextPathMatcher, HttpServerChannelHandler>> it = candidates.iterator(); + while (it.hasNext()) { + Map.Entry<ContextPathMatcher, HttpServerChannelHandler> entry = it.next(); + if (entry.getKey().matchesRest(path, false)) { + answer = entry.getValue(); + break; + } + } + + // then match by non wildcard path + if (answer == null) { + it = candidates.iterator(); + while (it.hasNext()) { + Map.Entry<ContextPathMatcher, HttpServerChannelHandler> entry = it.next(); + if (!entry.getKey().matchesRest(path, true)) { + it.remove(); + } + } + + // there should only be one + if (candidates.size() == 1) { + answer = candidates.get(0).getValue(); + } + } + + // fallback to regular matching + if (answer == null) { + for (Map.Entry<ContextPathMatcher, HttpServerChannelHandler> entry : consumers.entrySet()) { + if (entry.getKey().matches(path)) { + answer = entry.getValue(); + break; + } + } + } + + return answer; + } + + private static String pathAsKey(String path) { + // cater for default path + if (path == null || path.equals("/")) { + path = ""; + } + + // strip out query parameters + int idx = path.indexOf('?'); + if (idx > -1) { + path = path.substring(0, idx); + } + + // strip of ending / + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + + return UnsafeUriCharactersEncoder.encodeHttpURI(path); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/resources/META-INF/LICENSE.txt ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/resources/META-INF/LICENSE.txt b/components/camel-netty4-http/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 0000000..6b0b127 --- /dev/null +++ b/components/camel-netty4-http/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. + http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/resources/META-INF/NOTICE.txt ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/resources/META-INF/NOTICE.txt b/components/camel-netty4-http/src/main/resources/META-INF/NOTICE.txt new file mode 100644 index 0000000..2e215bf --- /dev/null +++ b/components/camel-netty4-http/src/main/resources/META-INF/NOTICE.txt @@ -0,0 +1,11 @@ + ========================================================================= + == NOTICE file corresponding to the section 4 d of == + == the Apache License, Version 2.0, == + == in this case for the Apache Camel distribution. == + ========================================================================= + + This product includes software developed by + The Apache Software Foundation (http://www.apache.org/). + + Please read the different LICENSE files present in the licenses directory of + this distribution. http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/resources/META-INF/services/org/apache/camel/TypeConverter ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/resources/META-INF/services/org/apache/camel/TypeConverter b/components/camel-netty4-http/src/main/resources/META-INF/services/org/apache/camel/TypeConverter new file mode 100644 index 0000000..b9e3cc7 --- /dev/null +++ b/components/camel-netty4-http/src/main/resources/META-INF/services/org/apache/camel/TypeConverter @@ -0,0 +1,18 @@ +# +# 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. +# + +org.apache.camel.component.netty4.http.NettyHttpConverter \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/main/resources/META-INF/services/org/apache/camel/component/netty4-http ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/resources/META-INF/services/org/apache/camel/component/netty4-http b/components/camel-netty4-http/src/main/resources/META-INF/services/org/apache/camel/component/netty4-http new file mode 100644 index 0000000..b26f6af --- /dev/null +++ b/components/camel-netty4-http/src/main/resources/META-INF/services/org/apache/camel/component/netty4-http @@ -0,0 +1,17 @@ +# 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. +# + +class=org.apache.camel.component.netty4.http.NettyHttpComponent http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/BaseNettyTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/BaseNettyTest.java b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/BaseNettyTest.java new file mode 100644 index 0000000..c511d89 --- /dev/null +++ b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/BaseNettyTest.java @@ -0,0 +1,95 @@ +/** + * 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.File; +import java.io.FileOutputStream; +import java.util.Properties; + +import org.apache.camel.CamelContext; +import org.apache.camel.component.properties.PropertiesComponent; +import org.apache.camel.converter.IOConverter; +import org.apache.camel.impl.JndiRegistry; +import org.apache.camel.test.AvailablePortFinder; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +/** + * + */ +public class BaseNettyTest extends CamelTestSupport { + private static volatile int port; + + @BeforeClass + public static void initPort() throws Exception { + File file = new File("target/nettyport.txt"); + + if (!file.exists()) { + // start from somewhere in the 26xxx range + port = AvailablePortFinder.getNextAvailable(26000); + } else { + // read port number from file + String s = IOConverter.toString(file, null); + port = Integer.parseInt(s); + // use next free port + port = AvailablePortFinder.getNextAvailable(port + 1); + } + + } + + @AfterClass + public static void savePort() throws Exception { + File file = new File("target/nettyport.txt"); + + // save to file, do not append + FileOutputStream fos = new FileOutputStream(file, false); + try { + fos.write(String.valueOf(port).getBytes()); + } finally { + fos.close(); + } + } + + @Override + protected CamelContext createCamelContext() throws Exception { + CamelContext context = super.createCamelContext(); + context.addComponent("properties", new PropertiesComponent("ref:prop")); + return context; + } + + @Override + protected JndiRegistry createRegistry() throws Exception { + JndiRegistry jndi = super.createRegistry(); + + Properties prop = new Properties(); + prop.setProperty("port", "" + getPort()); + jndi.bind("prop", prop); + + return jndi; + } + + protected int getNextPort() { + port = AvailablePortFinder.getNextAvailable(port + 1); + return port; + } + + protected int getPort() { + return port; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/ManagedNettyEndpointTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/ManagedNettyEndpointTest.java b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/ManagedNettyEndpointTest.java new file mode 100644 index 0000000..0363450 --- /dev/null +++ b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/ManagedNettyEndpointTest.java @@ -0,0 +1,81 @@ +/** + * 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.Set; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.junit.Test; + +public class ManagedNettyEndpointTest extends BaseNettyTest { + + @Override + protected boolean useJmx() { + return true; + } + + protected CamelContext createCamelContext() throws Exception { + CamelContext context = super.createCamelContext(); + return context; + } + + protected MBeanServer getMBeanServer() { + return context.getManagementStrategy().getManagementAgent().getMBeanServer(); + } + + @Test + public void testManagement() throws Exception { + // JMX tests dont work well on AIX CI servers (hangs them) + if (isPlatform("aix")) { + return; + } + + // should not add 10 endpoints + getMockEndpoint("mock:foo").expectedMessageCount(10); + for (int i = 0; i < 10; i++) { + String out = template.requestBody("netty4-http:http://localhost:{{port}}/foo?param" + i + "=value" + i, "Hello World", String.class); + assertEquals("param" + i + "=value" + i, out); + } + assertMockEndpointsSatisfied(); + + MBeanServer mbeanServer = getMBeanServer(); + + ObjectName on = ObjectName.getInstance("org.apache.camel:context=camel-1,type=endpoints,name=\"http://0.0.0.0:" + getPort() + "/foo\""); + mbeanServer.isRegistered(on); + + // should only be 2 endpoints in JMX + Set<ObjectName> set = getMBeanServer().queryNames(new ObjectName("*:context=camel-1,type=endpoints,*"), null); + assertEquals(2, set.size()); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("netty4-http:http://0.0.0.0:{{port}}/foo") + .to("mock:foo") + .transform().header(Exchange.HTTP_QUERY); + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/MyLoginModule.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/MyLoginModule.java b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/MyLoginModule.java new file mode 100644 index 0000000..2954688 --- /dev/null +++ b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/MyLoginModule.java @@ -0,0 +1,102 @@ +/** + * 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.IOException; +import java.util.Map; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; + +public class MyLoginModule implements LoginModule { + + private Subject subject; + private CallbackHandler callbackHandler; + + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { + this.subject = subject; + this.callbackHandler = callbackHandler; + } + + @Override + public boolean login() throws LoginException { + + // get username and password + Callback[] callbacks = new Callback[2]; + callbacks[0] = new NameCallback("username"); + callbacks[1] = new PasswordCallback("password", false); + + try { + callbackHandler.handle(callbacks); + String username = ((NameCallback)callbacks[0]).getName(); + char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword(); + String password = new String(tmpPassword); + ((PasswordCallback)callbacks[1]).clearPassword(); + + // only allow login if password is secret + // as this is just for testing purpose + if (!"secret".equals(password)) { + throw new LoginException("Login denied"); + } + + // add roles + if ("scott".equals(username)) { + subject.getPrincipals().add(new MyRolePrincipal("admin")); + subject.getPrincipals().add(new MyRolePrincipal("guest")); + } else if ("guest".equals(username)) { + subject.getPrincipals().add(new MyRolePrincipal("guest")); + } + + } catch (IOException ioe) { + LoginException le = new LoginException(ioe.toString()); + le.initCause(ioe); + throw le; + } catch (UnsupportedCallbackException uce) { + LoginException le = new LoginException("Error: " + uce.getCallback().toString() + + " not available to gather authentication information from the user"); + le.initCause(uce); + throw le; + } + + + return true; + } + + @Override + public boolean commit() throws LoginException { + return true; + } + + @Override + public boolean abort() throws LoginException { + return true; + } + + @Override + public boolean logout() throws LoginException { + subject = null; + callbackHandler = null; + return true; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/MyRolePrincipal.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/MyRolePrincipal.java b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/MyRolePrincipal.java new file mode 100644 index 0000000..3d11406 --- /dev/null +++ b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/MyRolePrincipal.java @@ -0,0 +1,33 @@ +/** + * 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; + +public class MyRolePrincipal implements Principal { + + private final String role; + + public MyRolePrincipal(String role) { + this.role = role; + } + + @Override + public String getName() { + return role; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttp500ErrorTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttp500ErrorTest.java b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttp500ErrorTest.java new file mode 100644 index 0000000..ef8ddbe --- /dev/null +++ b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttp500ErrorTest.java @@ -0,0 +1,66 @@ +/** + * 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 org.apache.camel.CamelExecutionException; +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.junit.Test; + +public class NettyHttp500ErrorTest extends BaseNettyTest { + + @Test + public void testHttp500Error() throws Exception { + getMockEndpoint("mock:input").expectedBodiesReceived("Hello World"); + + try { + template.requestBody("netty4-http:http://localhost:{{port}}/foo", "Hello World", String.class); + fail("Should have failed"); + } catch (CamelExecutionException e) { + NettyHttpOperationFailedException cause = assertIsInstanceOf(NettyHttpOperationFailedException.class, e.getCause()); + assertEquals(500, cause.getStatusCode()); + assertEquals("Camel cannot do this", context.getTypeConverter().convertTo(String.class, cause.getHttpContent().content())); + } + + assertMockEndpointsSatisfied(); + } + + @Test + public void testHttp500ErrorDisabled() throws Exception { + getMockEndpoint("mock:input").expectedBodiesReceived("Hello World"); + + String body = template.requestBody("netty4-http:http://localhost:{{port}}/foo?throwExceptionOnFailure=false", "Hello World", String.class); + assertEquals("Camel cannot do this", body); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("netty4-http:http://0.0.0.0:{{port}}/foo") + .to("mock:input") + // trigger failure by setting error code to 500 + .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(500)) + .setBody().constant("Camel cannot do this"); + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttp500ErrorThrowExceptionOnServerTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttp500ErrorThrowExceptionOnServerTest.java b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttp500ErrorThrowExceptionOnServerTest.java new file mode 100644 index 0000000..b5aa37b --- /dev/null +++ b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttp500ErrorThrowExceptionOnServerTest.java @@ -0,0 +1,67 @@ +/** + * 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 org.apache.camel.CamelExecutionException; +import org.apache.camel.builder.RouteBuilder; +import org.junit.Test; + +public class NettyHttp500ErrorThrowExceptionOnServerTest extends BaseNettyTest { + + @Test + public void testHttp500Error() throws Exception { + getMockEndpoint("mock:input").expectedBodiesReceived("Hello World"); + + try { + template.requestBody("netty4-http:http://localhost:{{port}}/foo", "Hello World", String.class); + fail("Should have failed"); + } catch (CamelExecutionException e) { + NettyHttpOperationFailedException cause = assertIsInstanceOf(NettyHttpOperationFailedException.class, e.getCause()); + assertEquals(500, cause.getStatusCode()); + String trace = context.getTypeConverter().convertTo(String.class, cause.getHttpContent().content()); + assertNotNull(trace); + assertTrue(trace.startsWith("java.lang.IllegalArgumentException: Camel cannot do this")); + assertEquals("http://localhost:" + getPort() + "/foo", cause.getUri()); + } + + assertMockEndpointsSatisfied(); + } + + @Test + public void testHttp500ErrorDisabled() throws Exception { + getMockEndpoint("mock:input").expectedBodiesReceived("Hello World"); + + String body = template.requestBody("netty4-http:http://localhost:{{port}}/foo?throwExceptionOnFailure=false", "Hello World", String.class); + assertTrue(body.startsWith("java.lang.IllegalArgumentException: Camel cannot do this")); + + assertMockEndpointsSatisfied(); + } + + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("netty4-http:http://0.0.0.0:{{port}}/foo") + .to("mock:input") + .throwException(new IllegalArgumentException("Camel cannot do this")); + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/db88eeda/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttpAccessHttpRequestAndResponseBeanTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttpAccessHttpRequestAndResponseBeanTest.java b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttpAccessHttpRequestAndResponseBeanTest.java new file mode 100644 index 0000000..6e32b85 --- /dev/null +++ b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttpAccessHttpRequestAndResponseBeanTest.java @@ -0,0 +1,74 @@ +/** + * 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.nio.charset.Charset; + +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaders; +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.builder.RouteBuilder; +import org.apache.camel.component.netty4.NettyConverter; +import org.junit.Test; + +public class NettyHttpAccessHttpRequestAndResponseBeanTest extends BaseNettyTest { + + @Test + public void testRawHttpRequestAndResponseInBean() throws Exception { + getMockEndpoint("mock:input").expectedBodiesReceived("World", "Camel"); + + String out = template.requestBody("netty4-http:http://localhost:{{port}}/foo", "World", String.class); + assertEquals("Bye World", out); + + String out2 = template.requestBody("netty4-http:http://localhost:{{port}}/foo", "Camel", String.class); + assertEquals("Bye Camel", out2); + + assertMockEndpointsSatisfied(); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("netty4-http:http://0.0.0.0:{{port}}/foo") + .to("mock:input") + .transform().method(NettyHttpAccessHttpRequestAndResponseBeanTest.class, "myTransformer"); + } + }; + } + + /** + * We can use both a netty http request and response type for transformation + */ + public static HttpResponse myTransformer(FullHttpRequest request) { + String in = request.content().toString(Charset.forName("UTF-8")); + String reply = "Bye " + in; + + HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, + NettyConverter.toByteBuffer(reply.getBytes())); + + response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, reply.length()); + + return response; + } + +}