Repository: camel
Updated Branches:
  refs/heads/camel-2.17.x 8ebe5d68a -> 1749f36c4


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/1749f36c
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/1749f36c
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/1749f36c

Branch: refs/heads/camel-2.17.x
Commit: 1749f36c451540c2bd0090a6228d44b2ce66ce97
Parents: 8ebe5d6
Author: Edward Welch <e...@edjusted.com>
Authored: Thu Apr 7 07:48:16 2016 -0400
Committer: Andrea Cosentino <anco...@gmail.com>
Committed: Fri Apr 8 09:37:20 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/1749f36c/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 d6cea0a..40e6d5b 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;
@@ -246,6 +251,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/1749f36c/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/1749f36c/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/1749f36c/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???");
             }
         };
     }

Reply via email to