CAMEL-9658 - Path gets decoded when bridging HTTP endpoints
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/8f11babb Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/8f11babb Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/8f11babb Branch: refs/heads/camel-2.16.x Commit: 8f11babb72927f2b88e3c87b1b81d1acc621b29a Parents: 5d84ac7 Author: Tadayoshi Sato <sato.tadayo...@gmail.com> Authored: Tue Mar 1 22:04:59 2016 +0900 Committer: Claus Ibsen <davscl...@apache.org> Committed: Fri Mar 4 10:44:47 2016 +0100 ---------------------------------------------------------------------- .../camel/http/common/DefaultHttpBinding.java | 14 ++++++-- .../jetty/CamelContinuationServlet.java | 22 +++++++----- .../jetty/HttpBridgeEncodedPathTest.java | 37 +++++++++++++++++--- .../netty/http/DefaultNettyHttpBinding.java | 2 +- .../http/NettyHttpBridgeEncodedPathTest.java | 36 +++++++++++++++++-- .../netty4/http/DefaultNettyHttpBinding.java | 2 +- .../http/NettyHttpBridgeEncodedPathTest.java | 36 +++++++++++++++++-- 7 files changed, 128 insertions(+), 21 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/8f11babb/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java ---------------------------------------------------------------------- diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java index 04f5851..ad9adfe 100644 --- a/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java @@ -137,11 +137,12 @@ public class DefaultHttpBinding implements HttpBinding { } // store the method and query and other info in headers as String types + String rawPath = getRawPath(request); headers.put(Exchange.HTTP_METHOD, request.getMethod()); headers.put(Exchange.HTTP_QUERY, request.getQueryString()); headers.put(Exchange.HTTP_URL, request.getRequestURL().toString()); headers.put(Exchange.HTTP_URI, request.getRequestURI()); - headers.put(Exchange.HTTP_PATH, request.getPathInfo()); + headers.put(Exchange.HTTP_PATH, rawPath); headers.put(Exchange.CONTENT_TYPE, request.getContentType()); if (LOG.isTraceEnabled()) { @@ -149,7 +150,7 @@ public class DefaultHttpBinding implements HttpBinding { LOG.trace("HTTP query {}", request.getQueryString()); LOG.trace("HTTP url {}", request.getRequestURL()); LOG.trace("HTTP uri {}", request.getRequestURI()); - LOG.trace("HTTP path {}", request.getPathInfo()); + LOG.trace("HTTP path {}", rawPath); LOG.trace("HTTP content-type {}", request.getContentType()); } @@ -224,7 +225,14 @@ public class DefaultHttpBinding implements HttpBinding { } } } - + + private String getRawPath(HttpServletRequest request) { + String uri = request.getRequestURI(); + String contextPath = request.getContextPath(); + String servletPath = request.getServletPath(); + return uri.substring(contextPath.length() + servletPath.length()); + } + protected void populateAttachments(HttpServletRequest request, HttpMessage message) { // check if there is multipart files, if so will put it into DataHandler Enumeration<?> names = request.getAttributeNames(); http://git-wip-us.apache.org/repos/asf/camel/blob/8f11babb/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/CamelContinuationServlet.java ---------------------------------------------------------------------- diff --git a/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/CamelContinuationServlet.java b/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/CamelContinuationServlet.java index a6a2e9c..9e77b29 100644 --- a/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/CamelContinuationServlet.java +++ b/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/CamelContinuationServlet.java @@ -35,6 +35,7 @@ import org.apache.camel.http.common.HttpHelper; import org.apache.camel.http.common.HttpMessage; import org.apache.camel.impl.DefaultExchange; import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.UnsafeUriCharactersEncoder; import org.eclipse.jetty.continuation.Continuation; import org.eclipse.jetty.continuation.ContinuationSupport; @@ -161,14 +162,8 @@ public class CamelContinuationServlet extends CamelServlet { // set context path as header String contextPath = consumer.getEndpoint().getPath(); exchange.getIn().setHeader("CamelServletContextPath", contextPath); - - String httpPath = (String)exchange.getIn().getHeader(Exchange.HTTP_PATH); - // here we just remove the CamelServletContextPath part from the HTTP_PATH - if (contextPath != null - && httpPath.startsWith(contextPath)) { - exchange.getIn().setHeader(Exchange.HTTP_PATH, - httpPath.substring(contextPath.length())); - } + + updateHttpPath(exchange, contextPath); if (log.isTraceEnabled()) { log.trace("Suspending continuation of exchangeId: {}", exchange.getExchangeId()); @@ -241,6 +236,17 @@ public class CamelContinuationServlet extends CamelServlet { } } + private void updateHttpPath(Exchange exchange, String contextPath) { + String httpPath = (String) exchange.getIn().getHeader(Exchange.HTTP_PATH); + // encode context path in case it contains unsafe chars, because HTTP_PATH isn't decoded at this moment + String encodedContextPath = UnsafeUriCharactersEncoder.encodeHttpURI(contextPath); + + // here we just remove the CamelServletContextPath part from the HTTP_PATH + if (contextPath != null && httpPath.startsWith(encodedContextPath)) { + exchange.getIn().setHeader(Exchange.HTTP_PATH, httpPath.substring(encodedContextPath.length())); + } + } + @Override public void destroy() { expiredExchanges.clear(); http://git-wip-us.apache.org/repos/asf/camel/blob/8f11babb/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/HttpBridgeEncodedPathTest.java ---------------------------------------------------------------------- diff --git a/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/HttpBridgeEncodedPathTest.java b/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/HttpBridgeEncodedPathTest.java index d1e3410..2ad8ca8 100644 --- a/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/HttpBridgeEncodedPathTest.java +++ b/components/camel-jetty9/src/test/java/org/apache/camel/component/jetty/HttpBridgeEncodedPathTest.java @@ -17,29 +17,55 @@ package org.apache.camel.component.jetty; import java.io.ByteArrayInputStream; +import java.net.URLEncoder; import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.GetMethod; import org.junit.Test; public class HttpBridgeEncodedPathTest extends BaseJettyTest { private int port1; private int port2; + private int port3; + private int port4; @Test - public void testHttpClient() throws Exception { + public void testEncodedQuery() throws Exception { String response = template.requestBodyAndHeader("http://localhost:" + port2 + "/test/hello?param1=%2B447777111222", new ByteArrayInputStream("This is a test".getBytes()), "Content-Type", "text/plain", String.class); assertEquals("Get a wrong response", "param1=+447777111222", response); } + @Test + public void testEncodedPath() throws Exception { + String path = URLEncoder.encode(" :/?#[]@!$", "UTF-8") + "/" + URLEncoder.encode("&'()+,;=", "UTF-8"); + MockEndpoint mock = getMockEndpoint("mock:encodedPath"); + mock.message(0).header(Exchange.HTTP_URI).isEqualTo("/" + path); + mock.message(0).header(Exchange.HTTP_PATH).isEqualTo(path); + mock.message(0).header(Exchange.HTTP_QUERY).isNull(); + mock.message(0).header(Exchange.HTTP_RAW_QUERY).isNull(); + + // cannot use template as it automatically decodes some chars in the path + HttpClient httpClient = new HttpClient(); + GetMethod httpGet = new GetMethod("http://localhost:" + port4 + "/test/" + path); + int status = httpClient.executeMethod(httpGet); + + assertEquals("Get a wrong response status", 200, status); + assertMockEndpointsSatisfied(); + } + protected RouteBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { public void configure() { port1 = getPort(); - port2 = getNextPort(); + port2 = getPort2(); + port3 = getNextPort(); + port4 = getNextPort(); errorHandler(noErrorHandler()); @@ -56,8 +82,11 @@ public class HttpBridgeEncodedPathTest extends BaseJettyTest { }; from("jetty:http://localhost:" + port2 + "/test/hello") .to("http://localhost:" + port1 + "?throwExceptionOnFailure=false&bridgeEndpoint=true"); - - from("jetty://http://localhost:" + port1 + "?matchOnUriPrefix=true").process(serviceProc); + from("jetty:http://localhost:" + port1 + "?matchOnUriPrefix=true").process(serviceProc); + + from("jetty:http://localhost:" + port4 + "/test?matchOnUriPrefix=true") + .to("http://localhost:" + port3 + "?throwExceptionOnFailure=false&bridgeEndpoint=true"); + from("jetty:http://localhost:" + port3 + "?matchOnUriPrefix=true").to("mock:encodedPath"); } }; } http://git-wip-us.apache.org/repos/asf/camel/blob/8f11babb/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultNettyHttpBinding.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultNettyHttpBinding.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultNettyHttpBinding.java index 05bd551..192d4c1 100644 --- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultNettyHttpBinding.java +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/DefaultNettyHttpBinding.java @@ -132,7 +132,7 @@ public class DefaultNettyHttpBinding implements NettyHttpBinding, Cloneable { headers.put(Exchange.HTTP_RAW_QUERY, uri.getRawQuery()); // strip the starting endpoint path so the path is relative to the endpoint uri - String path = uri.getPath(); + String path = uri.getRawPath(); if (configuration.getPath() != null) { // need to match by lower case as we want to ignore case on context-path String matchPath = path.toLowerCase(Locale.US); http://git-wip-us.apache.org/repos/asf/camel/blob/8f11babb/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBridgeEncodedPathTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBridgeEncodedPathTest.java b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBridgeEncodedPathTest.java index 25a3bff..4c51795 100644 --- a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBridgeEncodedPathTest.java +++ b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpBridgeEncodedPathTest.java @@ -16,28 +16,54 @@ */ package org.apache.camel.component.netty.http; +import java.net.URLEncoder; + import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.GetMethod; import org.junit.Test; public class NettyHttpBridgeEncodedPathTest extends BaseNettyTest { private int port1; private int port2; + private int port3; + private int port4; @Test - public void testHttpClient() throws Exception { + public void testEncodedQuery() throws Exception { String response = template.requestBody("http://localhost:" + port2 + "/nettyTestRouteA?param1=%2B447777111222", null, String.class); assertEquals("Get a wrong response", "param1=+447777111222", response); } + @Test + public void testEncodedPath() throws Exception { + String path = URLEncoder.encode(" :/?#[]@!$", "UTF-8") + "/" + URLEncoder.encode("&'()+,;=", "UTF-8"); + MockEndpoint mock = getMockEndpoint("mock:encodedPath"); + mock.message(0).header(Exchange.HTTP_PATH).isEqualTo("/" + path); + mock.message(0).header(Exchange.HTTP_QUERY).isNull(); + mock.message(0).header(Exchange.HTTP_RAW_QUERY).isNull(); + + // cannot use template as it automatically decodes some chars in the path + HttpClient httpClient = new HttpClient(); + GetMethod httpGet = new GetMethod("http://localhost:" + port4 + "/nettyTestRouteC/" + path); + int status = httpClient.executeMethod(httpGet); + + assertEquals("Get a wrong response status", 200, status); + assertMockEndpointsSatisfied(); + } + protected RouteBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { public void configure() { port1 = getPort(); port2 = getNextPort(); + port3 = getNextPort(); + port4 = getNextPort(); errorHandler(noErrorHandler()); @@ -55,10 +81,16 @@ public class NettyHttpBridgeEncodedPathTest extends BaseNettyTest { from("netty-http:http://localhost:" + port2 + "/nettyTestRouteA?matchOnUriPrefix=true") .log("Using NettyTestRouteA route: CamelHttpPath=[${header.CamelHttpPath}], CamelHttpUri=[${header.CamelHttpUri}]") .to("netty-http:http://localhost:" + port1 + "/nettyTestRouteB?throwExceptionOnFailure=false&bridgeEndpoint=true"); - from("netty-http:http://localhost:" + port1 + "/nettyTestRouteB?matchOnUriPrefix=true") .log("Using NettyTestRouteB route: CamelHttpPath=[${header.CamelHttpPath}], CamelHttpUri=[${header.CamelHttpUri}]") .process(serviceProc); + + from("netty-http:http://localhost:" + port4 + "/nettyTestRouteC?matchOnUriPrefix=true") + .log("Using NettyTestRouteC route: CamelHttpPath=[${header.CamelHttpPath}], CamelHttpUri=[${header.CamelHttpUri}]") + .to("netty-http:http://localhost:" + port3 + "/nettyTestRouteD?throwExceptionOnFailure=false&bridgeEndpoint=true"); + from("netty-http:http://localhost:" + port3 + "/nettyTestRouteD?matchOnUriPrefix=true") + .log("Using NettyTestRouteD route: CamelHttpPath=[${header.CamelHttpPath}], CamelHttpUri=[${header.CamelHttpUri}]") + .to("mock:encodedPath"); } }; } http://git-wip-us.apache.org/repos/asf/camel/blob/8f11babb/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultNettyHttpBinding.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultNettyHttpBinding.java b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultNettyHttpBinding.java index 0a183a3..552f5de 100644 --- a/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultNettyHttpBinding.java +++ b/components/camel-netty4-http/src/main/java/org/apache/camel/component/netty4/http/DefaultNettyHttpBinding.java @@ -134,7 +134,7 @@ public class DefaultNettyHttpBinding implements NettyHttpBinding, Cloneable { headers.put(Exchange.HTTP_RAW_QUERY, uri.getRawQuery()); // strip the starting endpoint path so the path is relative to the endpoint uri - String path = uri.getPath(); + String path = uri.getRawPath(); if (configuration.getPath() != null) { // need to match by lower case as we want to ignore case on context-path String matchPath = path.toLowerCase(Locale.US); http://git-wip-us.apache.org/repos/asf/camel/blob/8f11babb/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttpBridgeEncodedPathTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttpBridgeEncodedPathTest.java b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttpBridgeEncodedPathTest.java index a2c9b3c..76d02ee 100644 --- a/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttpBridgeEncodedPathTest.java +++ b/components/camel-netty4-http/src/test/java/org/apache/camel/component/netty4/http/NettyHttpBridgeEncodedPathTest.java @@ -16,28 +16,54 @@ */ package org.apache.camel.component.netty4.http; +import java.net.URLEncoder; + import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.GetMethod; import org.junit.Test; public class NettyHttpBridgeEncodedPathTest extends BaseNettyTest { private int port1; private int port2; + private int port3; + private int port4; @Test - public void testHttpClient() throws Exception { + public void testEncodedQuery() throws Exception { String response = template.requestBody("http://localhost:" + port2 + "/nettyTestRouteA?param1=%2B447777111222", null, String.class); assertEquals("Get a wrong response", "param1=+447777111222", response); } + @Test + public void testEncodedPath() throws Exception { + String path = URLEncoder.encode(" :/?#[]@!$", "UTF-8") + "/" + URLEncoder.encode("&'()+,;=", "UTF-8"); + MockEndpoint mock = getMockEndpoint("mock:encodedPath"); + mock.message(0).header(Exchange.HTTP_PATH).isEqualTo("/" + path); + mock.message(0).header(Exchange.HTTP_QUERY).isNull(); + mock.message(0).header(Exchange.HTTP_RAW_QUERY).isNull(); + + // cannot use template as it automatically decodes some chars in the path + HttpClient httpClient = new HttpClient(); + GetMethod httpGet = new GetMethod("http://localhost:" + port4 + "/nettyTestRouteC/" + path); + int status = httpClient.executeMethod(httpGet); + + assertEquals("Get a wrong response status", 200, status); + assertMockEndpointsSatisfied(); + } + protected RouteBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { public void configure() { port1 = getPort(); port2 = getNextPort(); + port3 = getNextPort(); + port4 = getNextPort(); errorHandler(noErrorHandler()); @@ -55,10 +81,16 @@ public class NettyHttpBridgeEncodedPathTest extends BaseNettyTest { from("netty4-http:http://localhost:" + port2 + "/nettyTestRouteA?matchOnUriPrefix=true") .log("Using NettyTestRouteA route: CamelHttpPath=[${header.CamelHttpPath}], CamelHttpUri=[${header.CamelHttpUri}]") .to("netty4-http:http://localhost:" + port1 + "/nettyTestRouteB?throwExceptionOnFailure=false&bridgeEndpoint=true"); - from("netty4-http:http://localhost:" + port1 + "/nettyTestRouteB?matchOnUriPrefix=true") .log("Using NettyTestRouteB route: CamelHttpPath=[${header.CamelHttpPath}], CamelHttpUri=[${header.CamelHttpUri}]") .process(serviceProc); + + from("netty4-http:http://localhost:" + port4 + "/nettyTestRouteC?matchOnUriPrefix=true") + .log("Using NettyTestRouteC route: CamelHttpPath=[${header.CamelHttpPath}], CamelHttpUri=[${header.CamelHttpUri}]") + .to("netty4-http:http://localhost:" + port3 + "/nettyTestRouteD?throwExceptionOnFailure=false&bridgeEndpoint=true"); + from("netty4-http:http://localhost:" + port3 + "/nettyTestRouteD?matchOnUriPrefix=true") + .log("Using NettyTestRouteD route: CamelHttpPath=[${header.CamelHttpPath}], CamelHttpUri=[${header.CamelHttpUri}]") + .to("mock:encodedPath"); } }; }