CAMEL-7354: Rest DSL. Integrate with camel-netty-http.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/248f8cd7 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/248f8cd7 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/248f8cd7 Branch: refs/heads/master Commit: 248f8cd74885521abe8e8f0456a4323d2733a79b Parents: d58fd11 Author: Claus Ibsen <davscl...@apache.org> Authored: Tue Jul 29 09:08:24 2014 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Tue Jul 29 10:12:10 2014 +0200 ---------------------------------------------------------------------- .../netty/http/DefaultContextPathMatcher.java | 4 + .../netty/http/NettyHttpComponent.java | 86 ++++++++++++++++-- .../netty/http/RestContextPathMatcher.java | 95 ++++++++++++++++++++ .../netty/http/RestNettyHttpBinding.java | 91 +++++++++++++++++++ .../HttpServerMultiplexChannelHandler.java | 9 +- .../netty/http/rest/RestNettyHttpGetTest.java | 56 ++++++++++++ 6 files changed, 331 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/248f8cd7/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultContextPathMatcher.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultContextPathMatcher.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultContextPathMatcher.java index 55bfb58..2efc357 100644 --- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultContextPathMatcher.java +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultContextPathMatcher.java @@ -42,6 +42,10 @@ public class DefaultContextPathMatcher implements ContextPathMatcher { } } + public String getPath() { + return path; + } + @Override public boolean equals(Object o) { if (this == o) { http://git-wip-us.apache.org/repos/asf/camel/blob/248f8cd7/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java index 3732454..90f09f1 100644 --- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/NettyHttpComponent.java @@ -18,15 +18,22 @@ package org.apache.camel.component.netty.http; import java.net.URI; import java.util.HashMap; +import java.util.Locale; import java.util.Map; +import org.apache.camel.CamelContext; +import org.apache.camel.Consumer; import org.apache.camel.Endpoint; +import org.apache.camel.Processor; import org.apache.camel.component.netty.NettyComponent; import org.apache.camel.component.netty.NettyConfiguration; import org.apache.camel.component.netty.NettyServerBootstrapConfiguration; import org.apache.camel.component.netty.http.handlers.HttpServerMultiplexChannelHandler; import org.apache.camel.spi.HeaderFilterStrategy; import org.apache.camel.spi.HeaderFilterStrategyAware; +import org.apache.camel.spi.RestConfiguration; +import org.apache.camel.spi.RestConsumerFactory; +import org.apache.camel.util.FileUtil; import org.apache.camel.util.IntrospectionSupport; import org.apache.camel.util.ServiceHelper; import org.apache.camel.util.URISupport; @@ -37,7 +44,7 @@ import org.slf4j.LoggerFactory; /** * Netty HTTP based component. */ -public class NettyHttpComponent extends NettyComponent implements HeaderFilterStrategyAware { +public class NettyHttpComponent extends NettyComponent implements HeaderFilterStrategyAware, RestConsumerFactory { private static final Logger LOG = LoggerFactory.getLogger(NettyHttpComponent.class); @@ -53,7 +60,8 @@ public class NettyHttpComponent extends NettyComponent implements HeaderFilterSt super(NettyHttpEndpoint.class); setConfiguration(new NettyHttpConfiguration()); setHeaderFilterStrategy(new NettyHttpHeaderFilterStrategy()); - setNettyHttpBinding(new DefaultNettyHttpBinding(getHeaderFilterStrategy())); + // use the binding that supports Rest DSL + setNettyHttpBinding(new RestNettyHttpBinding(getHeaderFilterStrategy())); } @Override @@ -102,12 +110,16 @@ public class NettyHttpComponent extends NettyComponent implements HeaderFilterSt NettyHttpEndpoint answer = new NettyHttpEndpoint(addressUri, this, config); answer.setTimer(getTimer()); - // set component options on endpoint as defaults - // As the component's NettyHttpBinding could be override by the setHeaderFilterStrategy - // Here we just create a new DefaultNettyHttpBinding here + // must use a copy of the binding on the endpoint to avoid sharing same instance that can cause side-effects if (answer.getNettyHttpBinding() == null) { - DefaultNettyHttpBinding nettyHttpBinding = (DefaultNettyHttpBinding)getNettyHttpBinding(); - answer.setNettyHttpBinding(nettyHttpBinding.copy()); + Object binding = getNettyHttpBinding(); + if (binding instanceof RestNettyHttpBinding) { + NettyHttpBinding copy = ((RestNettyHttpBinding) binding).copy(); + answer.setNettyHttpBinding(copy); + } else if (binding instanceof DefaultNettyHttpBinding) { + NettyHttpBinding copy = ((DefaultNettyHttpBinding) binding).copy(); + answer.setNettyHttpBinding(copy); + } } if (headerFilterStrategy != null) { answer.setHeaderFilterStrategy(headerFilterStrategy); @@ -199,7 +211,65 @@ public class NettyHttpComponent extends NettyComponent implements HeaderFilterSt } return answer; } - + + @Override + public Consumer createConsumer(CamelContext camelContext, Processor processor, String verb, String path, + String consumes, String produces, Map<String, Object> parameters) throws Exception { + + path = FileUtil.stripLeadingSeparator(path); + + String scheme = "http"; + String host = "0.0.0.0"; + int port = 0; + + // if no explicit port/host configured, then use port from rest configuration + RestConfiguration config = getCamelContext().getRestConfiguration(); + if (config != null && (config.getComponent() == null || config.getComponent().equals("netty-http"))) { + if (config.getScheme() != null) { + scheme = config.getScheme(); + } + if (config.getHost() != null) { + host = config.getHost(); + } + int num = config.getPort(); + if (num > 0) { + port = num; + } + } + + Map<String, Object> map = new HashMap<String, Object>(); + // build query string, and append any endpoint configuration properties + if (config != null && (config.getComponent() == null || config.getComponent().equals("netty-http"))) { + // setup endpoint options + if (config.getEndpointProperties() != null && !config.getEndpointProperties().isEmpty()) { + map.putAll(config.getEndpointProperties()); + } + } + + String query = URISupport.createQueryString(map); + + String url = "netty-http:%s://%s:%s/%s?httpMethodRestrict=%s"; + if (!query.isEmpty()) { + url = url + "?" + query; + } + + // must use upper case for restrict + String restrict = verb.toUpperCase(Locale.US); + + // get the endpoint + url = String.format(url, scheme, host, port, path, restrict); + NettyHttpEndpoint endpoint = camelContext.getEndpoint(url, NettyHttpEndpoint.class); + setProperties(endpoint, parameters); + + // configure consumer properties + Consumer consumer = endpoint.createConsumer(processor); + if (config != null && config.getConsumerProperties() != null && !config.getConsumerProperties().isEmpty()) { + setProperties(consumer, config.getConsumerProperties()); + } + + return consumer; + } + @Override protected void doStop() throws Exception { super.doStop(); http://git-wip-us.apache.org/repos/asf/camel/blob/248f8cd7/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestContextPathMatcher.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestContextPathMatcher.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestContextPathMatcher.java new file mode 100644 index 0000000..c48e815 --- /dev/null +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestContextPathMatcher.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.netty.http; + +/** + * A {@link org.apache.camel.component.netty.http.ContextPathMatcher} that supports the Rest DSL. + */ +public class RestContextPathMatcher extends DefaultContextPathMatcher { + + private final String rawPath; + + public RestContextPathMatcher(String rawPath, String path, boolean matchOnUriPrefix) { + super(path, matchOnUriPrefix); + this.rawPath = rawPath; + } + + @Override + public boolean matches(String target) { + if (useRestMatching(rawPath)) { + return matchRestPath(target, rawPath); + } else { + return super.matches(target); + } + } + + private boolean useRestMatching(String path) { + // only need to do rest matching if using { } placeholders + return path.indexOf('{') > -1; + } + + /** + * Matches the given request path with the configured consumer path + * + * @param requestPath the request path + * @param consumerPath the consumer path which may use { } tokens + * @return <tt>true</tt> if matched, <tt>false</tt> otherwise + */ + public boolean matchRestPath(String requestPath, String consumerPath) { + // remove starting/ending slashes + if (requestPath.startsWith("/")) { + requestPath = requestPath.substring(1); + } + if (requestPath.endsWith("/")) { + requestPath = requestPath.substring(0, requestPath.length() - 1); + } + // remove starting/ending slashes + if (consumerPath.startsWith("/")) { + consumerPath = consumerPath.substring(1); + } + if (consumerPath.endsWith("/")) { + consumerPath = consumerPath.substring(0, consumerPath.length() - 1); + } + + // split using single char / is optimized in the jdk + String[] requestPaths = requestPath.split("/"); + String[] consumerPaths = consumerPath.split("/"); + + // must be same number of path's + if (requestPaths.length != consumerPaths.length) { + return false; + } + + for (int i = 0; i < requestPaths.length; i++) { + String p1 = requestPaths[i]; + String p2 = consumerPaths[i]; + + if (p2.startsWith("{") && p2.endsWith("}")) { + // always matches + continue; + } + + if (!p1.equals(p2)) { + return false; + } + } + + // assume matching + return true; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/248f8cd7/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestNettyHttpBinding.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestNettyHttpBinding.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestNettyHttpBinding.java new file mode 100644 index 0000000..ea8c448 --- /dev/null +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/RestNettyHttpBinding.java @@ -0,0 +1,91 @@ +/** + * 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.netty.http; + +import java.net.URI; +import java.util.Map; + +import org.apache.camel.Exchange; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.spi.HeaderFilterStrategy; +import org.jboss.netty.handler.codec.http.HttpRequest; + +/** + * 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(HttpRequest 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/248f8cd7/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java index 801360c..aedd1e1 100644 --- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/handlers/HttpServerMultiplexChannelHandler.java @@ -25,6 +25,7 @@ import org.apache.camel.component.netty.http.ContextPathMatcher; import org.apache.camel.component.netty.http.DefaultContextPathMatcher; import org.apache.camel.component.netty.http.HttpServerConsumerChannelFactory; import org.apache.camel.component.netty.http.NettyHttpConsumer; +import org.apache.camel.component.netty.http.RestContextPathMatcher; import org.apache.camel.util.UnsafeUriCharactersEncoder; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.ChannelHandler; @@ -66,14 +67,18 @@ public class HttpServerMultiplexChannelHandler extends SimpleChannelUpstreamHand } public void addConsumer(NettyHttpConsumer consumer) { + String rawPath = consumer.getConfiguration().getPath(); String path = pathAsKey(consumer.getConfiguration().getPath()); - ContextPathMatcher matcher = new DefaultContextPathMatcher(path, consumer.getConfiguration().isMatchOnUriPrefix()); + // 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()); - ContextPathMatcher matcher = new DefaultContextPathMatcher(path, consumer.getConfiguration().isMatchOnUriPrefix()); + // use rest path matcher in case Rest DSL is in use + ContextPathMatcher matcher = new RestContextPathMatcher(rawPath, path, consumer.getConfiguration().isMatchOnUriPrefix()); consumers.remove(matcher); } http://git-wip-us.apache.org/repos/asf/camel/blob/248f8cd7/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/rest/RestNettyHttpGetTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/rest/RestNettyHttpGetTest.java b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/rest/RestNettyHttpGetTest.java new file mode 100644 index 0000000..d36db7f --- /dev/null +++ b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/rest/RestNettyHttpGetTest.java @@ -0,0 +1,56 @@ +/** + * 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.netty.http.rest; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.netty.http.BaseNettyTest; +import org.junit.Test; + +public class RestNettyHttpGetTest extends BaseNettyTest { + + @Test + public void testProducerGet() throws Exception { + String out = template.requestBody("netty-http:http://localhost:{{port}}/users/123/basic", null, String.class); + assertEquals("123;Donald Duck", out); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + // configure to use netty-http on localhost with the given port + restConfiguration().component("netty-http").host("localhost").port(getPort()); + + // use the rest DSL to define the rest services + rest("/users/") + .get("{id}/basic") + .route() + .to("mock:input") + .process(new Processor() { + public void process(Exchange exchange) throws Exception { + String id = exchange.getIn().getHeader("id", String.class); + exchange.getOut().setBody(id + ";Donald Duck"); + } + }); + } + }; + } + +}