You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2016/04/08 09:35:07 UTC

[4/5] camel git commit: Adding a new endpoint property to the HttpCommonEndpoint which allows preserving the Host header in reverse proxy applications, this class is ued by the Http, Http4, and Jetty producers. Updated the HttpProducer (Jetty/HTTP4) to s

Adding a new endpoint property to the HttpCommonEndpoint which allows preserving the Host header in reverse proxy applications, this class is ued by the Http, Http4, and Jetty producers.
Updated the HttpProducer (Jetty/HTTP4) to set the Host header when this flag is enabled.  The older HTTP component does not readily let us override the Host header, this component will not support this parameter
Updated the Integration Test to validate the behavior for both when the new parameter is set, and unset.


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/d210eddd
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/d210eddd
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/d210eddd

Branch: refs/heads/master
Commit: d210eddd39e76372deae5f6003933ed90cf7dce5
Parents: d43cd77
Author: Edward Welch <ed...@edjusted.com>
Authored: Thu Apr 7 07:48:16 2016 -0400
Committer: Andrea Cosentino <an...@gmail.com>
Committed: Fri Apr 8 09:25:58 2016 +0200

----------------------------------------------------------------------
 .../camel/http/common/HttpCommonEndpoint.java   | 18 ++++
 .../camel/component/http4/HttpProducer.java     | 12 ++-
 .../component/jetty/JettyHttpProducer.java      | 12 ++-
 .../jetty/JettyBridgeHostHeaderIssueTest.java   | 90 ++++++++++++++++++--
 4 files changed, 122 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/d210eddd/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java
----------------------------------------------------------------------
diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java
index fb4e73b..beaf002 100644
--- a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java
+++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java
@@ -46,6 +46,11 @@ public abstract class HttpCommonEndpoint extends DefaultEndpoint implements Head
             description = "If the option is true, HttpProducer will ignore the Exchange.HTTP_URI header, and use the endpoint's URI for request."
                     + " You may also set the option throwExceptionOnFailure to be false to let the HttpProducer send all the fault response back.")
     boolean bridgeEndpoint;
+    @UriParam(label = "producer",
+            description = "If the option is true, HttpProducer will set the Host header to the value contained in the current exchange Host header, " +
+                    "useful in reverse proxy applications where you want the Host header received by the downstream server to reflect the URL called by the upstream client, " +
+                    "this allows applications which use the Host header to generate accurate URL's for a proxied service")
+    boolean preserveHostHeader;
     @UriParam(label = "consumer",
             description = "Whether or not the consumer should try to find a target consumer by matching the URI prefix if no exact match is found.")
     boolean matchOnUriPrefix;
@@ -256,6 +261,19 @@ public abstract class HttpCommonEndpoint extends DefaultEndpoint implements Head
         this.bridgeEndpoint = bridge;
     }
 
+    public boolean isPreserveHostHeader() {
+        return preserveHostHeader;
+    }
+
+    /**
+     * If the option is true, HttpProducer will set the Host header to the value contained in the current exchange Host header,
+     * useful in reverse proxy applications where you want the Host header received by the downstream server to reflect the URL called by the upstream client,
+     * this allows applications which use the Host header to generate accurate URL's for a proxied service
+     */
+    public void setPreserveHostHeader(boolean preserveHostHeader) {
+        this.preserveHostHeader = preserveHostHeader;
+    }
+
     public boolean isMatchOnUriPrefix() {
         return matchOnUriPrefix;
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/d210eddd/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpProducer.java
----------------------------------------------------------------------
diff --git a/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpProducer.java b/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpProducer.java
index c59c3b8..3b38911 100644
--- a/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpProducer.java
+++ b/components/camel-http4/src/main/java/org/apache/camel/component/http4/HttpProducer.java
@@ -106,8 +106,6 @@ public class HttpProducer extends DefaultProducer {
             if (queryString != null) {
                 skipRequestHeaders = URISupport.parseQuery(queryString, false, true);
             }
-            // Need to remove the Host key as it should be not used
-            exchange.getIn().getHeaders().remove("host");
         }
         HttpRequestBase httpRequest = createMethod(exchange);
         Message in = exchange.getIn();
@@ -156,6 +154,16 @@ public class HttpProducer extends DefaultProducer {
             }
         }
 
+        //In reverse proxy applications it can be desirable for the downstream service to see the original Host header
+        //if this option is set, and the exchange Host header is not null, we will set it's current value on the httpRequest
+        if (getEndpoint().isPreserveHostHeader()) {
+            String hostHeader = exchange.getIn().getHeader("Host", String.class);
+            if (hostHeader != null) {
+                //HttpClient 4 will check to see if the Host header is present, and use it if it is, see org.apache.http.protocol.RequestTargetHost in httpcore
+                httpRequest.setHeader("Host", hostHeader);
+            }
+        }
+
         // lets store the result in the output message.
         HttpResponse httpResponse = null;
         try {

http://git-wip-us.apache.org/repos/asf/camel/blob/d210eddd/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpProducer.java
----------------------------------------------------------------------
diff --git a/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpProducer.java b/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpProducer.java
index 10f7186..2a01b39 100644
--- a/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpProducer.java
+++ b/components/camel-jetty-common/src/main/java/org/apache/camel/component/jetty/JettyHttpProducer.java
@@ -185,8 +185,6 @@ public class JettyHttpProducer extends DefaultAsyncProducer implements AsyncProc
             if (queryString != null) {
                 skipRequestHeaders = URISupport.parseQuery(queryString, false, true);
             }
-            // Need to remove the Host key as it should be not used 
-            exchange.getIn().getHeaders().remove("host");
         }
 
         // propagate headers as HTTP headers
@@ -228,6 +226,16 @@ public class JettyHttpProducer extends DefaultAsyncProducer implements AsyncProc
             }
         }
 
+        //In reverse proxy applications it can be desirable for the downstream service to see the original Host header
+        //if this option is set, and the exchange Host header is not null, we will set it's current value on the httpExchange
+        if (getEndpoint().isPreserveHostHeader()) {
+            String hostHeader = exchange.getIn().getHeader("Host", String.class);
+            if (hostHeader != null) {
+                //HttpClient 4 will check to see if the Host header is present, and use it if it is, see org.apache.http.protocol.RequestTargetHost in httpcore
+                httpExchange.addRequestHeader("Host", hostHeader);
+            }
+        }
+
         // set the callback, which will handle all the response logic
         if (LOG.isDebugEnabled()) {
             LOG.debug("Sending HTTP request to: {}", httpExchange.getUrl());

http://git-wip-us.apache.org/repos/asf/camel/blob/d210eddd/tests/camel-itest/src/test/java/org/apache/camel/itest/jetty/JettyBridgeHostHeaderIssueTest.java
----------------------------------------------------------------------
diff --git a/tests/camel-itest/src/test/java/org/apache/camel/itest/jetty/JettyBridgeHostHeaderIssueTest.java b/tests/camel-itest/src/test/java/org/apache/camel/itest/jetty/JettyBridgeHostHeaderIssueTest.java
index 72074db..a55be28 100644
--- a/tests/camel-itest/src/test/java/org/apache/camel/itest/jetty/JettyBridgeHostHeaderIssueTest.java
+++ b/tests/camel-itest/src/test/java/org/apache/camel/itest/jetty/JettyBridgeHostHeaderIssueTest.java
@@ -28,13 +28,21 @@ public class JettyBridgeHostHeaderIssueTest extends CamelTestSupport {
     private int port;
     private int port2;
     private int port3;
+    private int port4;
+    private int port5;
+    private String receivedHostHeaderEndpoint1;
+    private String receivedHostHeaderEndpoint2;
+    private String receivedHostHeaderEndpoint3;
+    private String receivedHostHeaderEndpoint4;
 
     @Test
     public void testHostHeader() throws Exception {
-        // TODO: the host header is removed in bridgeEndpoint in the http4 producer, that seems wrong
-        // as Camel as a reverse-proxy should update the host header accordingly
 
+        //The first two calls will test http4 producers
 
+        //The first call to our service will hit the first destination in the round robin load balancer
+        //this destination has the preserveProxyHeader parameter set to true, so we verify the Host header
+        //received by our downstream instance matches the address and port of the proxied service
         Exchange reply = template.request("http4:localhost:" + port + "/myapp", new Processor() {
             @Override
             public void process(Exchange exchange) throws Exception {
@@ -43,7 +51,12 @@ public class JettyBridgeHostHeaderIssueTest extends CamelTestSupport {
         });
         assertNotNull(reply);
         assertEquals("foo", reply.getOut().getBody(String.class));
+        //assert the received Host header is localhost:port (where port matches the /myapp port)
+        assertEquals("localhost:" + port, receivedHostHeaderEndpoint1);
 
+        //The second call to our service will hit the second destination in the round robin load balancer
+        //this destination does not have the preserveProxyHeader, so we expect the Host header received by the destination
+        //to match the url of the destination service itself
         Exchange reply2 = template.request("http4:localhost:" + port + "/myapp", new Processor() {
             @Override
             public void process(Exchange exchange) throws Exception {
@@ -52,6 +65,35 @@ public class JettyBridgeHostHeaderIssueTest extends CamelTestSupport {
         });
         assertNotNull(reply2);
         assertEquals("bar", reply2.getOut().getBody(String.class));
+        //assert the received Host header is localhost:port3 (where port3 matches the /bar destination server)
+        assertEquals("localhost:" + port3, receivedHostHeaderEndpoint2);
+
+
+        //The next two calls will use/test the jetty producers in the round robin load balancer
+
+        //The first has the preserveHostHeader option set to true, so we would expect to receive a Host header matching the /myapp proxied service
+        Exchange reply3 = template.request("http4:localhost:" + port + "/myapp", new Processor() {
+            @Override
+            public void process(Exchange exchange) throws Exception {
+                exchange.getIn().setBody("Bye JWorld");
+            }
+        });
+        assertNotNull(reply3);
+        assertEquals("jbar", reply3.getOut().getBody(String.class));
+        //assert the received Host header is localhost:port (where port matches the /myapp destination server)
+        assertEquals("localhost:" + port, receivedHostHeaderEndpoint3);
+
+        //The second does not have a preserveHostHeader (preserveHostHeader=false), we would expect to see a Host header matching the destination service
+        Exchange reply4 = template.request("http4:localhost:" + port + "/myapp", new Processor() {
+            @Override
+            public void process(Exchange exchange) throws Exception {
+                exchange.getIn().setBody("JAVA!!!!");
+            }
+        });
+        assertNotNull(reply4);
+        assertEquals("java???", reply4.getOut().getBody(String.class));
+        //assert the received Host header is localhost:port5 (where port3 matches the /jbarf destination server)
+        assertEquals("localhost:" + port5, receivedHostHeaderEndpoint4);
     }
 
     @Override
@@ -59,18 +101,54 @@ public class JettyBridgeHostHeaderIssueTest extends CamelTestSupport {
         port = AvailablePortFinder.getNextAvailable(12000);
         port2 = AvailablePortFinder.getNextAvailable(12100);
         port3 = AvailablePortFinder.getNextAvailable(12200);
+        port4 = AvailablePortFinder.getNextAvailable(12300);
+        port5 = AvailablePortFinder.getNextAvailable(12400);
 
         return new RouteBuilder() {
             @Override
             public void configure() throws Exception {
                 from("jetty:http://localhost:" + port + "/myapp?matchOnUriPrefix=true")
                     .loadBalance().roundRobin()
-                        .to("http4://localhost:" + port2 + "/foo?bridgeEndpoint=true&throwExceptionOnFailure=false")
-                        .to("http4://localhost:" + port3 + "/bar?bridgeEndpoint=true&throwExceptionOnFailure=false");
+                        .to("http4://localhost:" + port2 + "/foo?bridgeEndpoint=true&throwExceptionOnFailure=false&preserveHostHeader=true")
+                        .to("http4://localhost:" + port3 + "/bar?bridgeEndpoint=true&throwExceptionOnFailure=false")
+                        .to("jetty:http://localhost:" + port4 + "/jbar?bridgeEndpoint=true&throwExceptionOnFailure=false&preserveHostHeader=true")
+                        .to("jetty:http://localhost:" + port5 + "/jbarf?bridgeEndpoint=true&throwExceptionOnFailure=false");
+
+                from("jetty:http://localhost:" + port2 + "/foo")
+                        .process(new Processor() {
+                            @Override
+                            public void process(Exchange exchange) throws Exception {
+                                receivedHostHeaderEndpoint1 = exchange.getIn().getHeader("Host", String.class);
+                            }
+                        })
+                        .transform().constant("foo");
+
+                from("jetty:http://localhost:" + port3 + "/bar")
+                        .process(new Processor() {
+                            @Override
+                            public void process(Exchange exchange) throws Exception {
+                                receivedHostHeaderEndpoint2 = exchange.getIn().getHeader("Host", String.class);
+                            }
+                        })
+                        .transform().constant("bar");
 
-                from("jetty:http://localhost:" + port2 + "/foo").transform().constant("foo");
+                from("jetty:http://localhost:" + port4 + "/jbar")
+                        .process(new Processor() {
+                            @Override
+                            public void process(Exchange exchange) throws Exception {
+                                receivedHostHeaderEndpoint3 = exchange.getIn().getHeader("Host", String.class);
+                            }
+                        })
+                        .transform().constant("jbar");
 
-                from("jetty:http://localhost:" + port3 + "/bar").transform().constant("bar");
+                from("jetty:http://localhost:" + port5 + "/jbarf")
+                        .process(new Processor() {
+                            @Override
+                            public void process(Exchange exchange) throws Exception {
+                                receivedHostHeaderEndpoint4 = exchange.getIn().getHeader("Host", String.class);
+                            }
+                        })
+                        .transform().constant("java???");
             }
         };
     }