Reputation: 163
I am trying to implement a simple HTTP proxy service with Apache Camel. My code looks like this:
from("jetty:http://localhost:80?matchOnUriPrefix=true")
.recipientList(simple("jetty:${in.header.CamelHttpUrl}?bridgeEndpoint=true&throwExceptionOnFailure=false&disableStreamCache=true"));
It is essentially this with a dynamic recipient list to support multiple destinations. I also had to add the disableStreamCache=true
bit, otherwise I would get weird exceptions with path duplication (like /index.html
would become /index.html/index.html
).
Nevertheless, it seems to work. But only with HTTP requests. When I try accessing an HTTPS site, I always get a 404.
According to the logs, the jetty component just doesn't seem to find the remote server. I have no idea why.
01:36:37.495 [qtp85415531-22 - www.google.cz:443] DEBUG org.eclipse.jetty.server.Server - REQUEST www.google.cz:443 on AsyncHttpConnection@6964b063,g=HttpGenerator{s=0,h=-1,b=-1,c=-1},p=HttpParser{s=-5,l=17,c=0},r=1
01:36:37.495 [qtp85415531-22 - www.google.cz:443] DEBUG o.e.j.server.handler.ContextHandler - scope null||www.google.cz:443 @ o.e.j.s.ServletContextHandler{/,null}
01:36:37.495 [qtp85415531-22 - www.google.cz:443] DEBUG o.e.j.server.handler.ContextHandler - context=null||www.google.cz:443 @ o.e.j.s.ServletContextHandler{/,null}
01:36:37.495 [qtp85415531-22 - www.google.cz:443] DEBUG o.e.jetty.servlet.ServletHandler - servlet null||www.google.cz:443 -> null
01:36:37.495 [qtp85415531-22 - www.google.cz:443] DEBUG o.e.jetty.servlet.ServletHandler - chain=null
01:36:37.495 [qtp85415531-22 - www.google.cz:443] DEBUG o.e.jetty.servlet.ServletHandler - Not Found www.google.cz:443
01:36:37.495 [qtp85415531-22 - www.google.cz:443] DEBUG org.eclipse.jetty.server.Server - RESPONSE www.google.cz:443 200 handled=false
What should I do to enable this HTTPS support? Is it even possible with standard Camel components?
Edit:
I managed to update my route definition to not use a recipient list. I don't know whether this improves anything performance-wise (does it?) but it feels better. I was also able to remove the path duplication problem when not using disableStreamCache=true
by this.
from("jetty:http://localhost:80?matchOnUriPrefix=true")
.to("http4:dummy?bridgeEndpoint=true&urlRewrite=#urlRewrite&throwExceptionOnFailure=false");
And the URL rewriter implementation:
UrlRewrite urlRewrite = new HttpServletUrlRewrite() {
@Override
public String rewrite(String url, String relativeUrl, Producer producer, HttpServletRequest request) throws Exception {
return request.getRequestURL().toString();
}
@Override
public String rewrite(String url, String relativeUrl, Producer producer) throws Exception {
return null;
}
};
Edit 2:
I should probably mention that I would like to intercept those requests and read/alter content (actually only HTTP headers). In effect I would like to implement an MITM proxy.
Edit 3:
I tried replacing the target component with log
to see whether the request gets through:
from("jetty:http://localhost:80?matchOnUriPrefix=true")
.to("log:test")
The message gets logged when used as a proxy with HTTP. It also gets logged when I replace the URI with jetty:https://localhost?matchOnUriPrefix=true
and try opening https://localhost
directly in the browser. However, when trying to use this as a proxy with HTTPS, I cannot get it to log. It seems like the Jetty component doesn't support this behavior. Is it correct?
I also tried using the Netty-http component with similar results (route tracer logged the CONNECT
request but the message doesn't get passed to the Log component)
Upvotes: 2
Views: 6801
Reputation: 13726
On most computer systems, localhost resolves to the IP address 127.0.0.1
Note:- This is a valid implementation in case of using Fiddler.***
<camelContext id="context" xmlns="http://camel.apache.org/schema/spring">
<properties>
<property key="http.proxyHost" value="127.0.0.1"/>
<property key="http.proxyPort" value="8888"/>
</properties>
</camelContext>
Upvotes: 0
Reputation: 163
The key was to define a handler for jetty that would take care of the CONNECT
method:
from("jetty:http://localhost:80?matchOnUriPrefix=true&handlers=#connectHandler")
The connectHandler
in this case can be either a Jetty ConnectHandler
(for normal HTTPS proxies) or a custom one (for MITM proxies):
class CustomConnectHandler extends ConnectHandler {
@Override
protected SocketChannel connect(HttpServletRequest request, String host, int port) throws IOException {
SocketChannel channel = SocketChannel.open();
channel.socket().setTcpNoDelay(true);
channel.socket().connect(new InetSocketAddress("localhost", 443), getConnectTimeout());
return channel;
}
}
This one connects to port 443 on the localhost and routes SSL data there. So we need to define another route from there:
from("jetty:https://localhost:443?matchOnUriPrefix=true")
(we can keep two separate routes or join them later by using a SEDA component)
To define custom SSL context parameters (key store, trust store, etc.), we can use the sslContextParametersRef
parameter on the endpoint.
Next, we need to alter the URL. We do this by populating the URI header with the full URL (it's only the relative path by default):
.setHeader(Exchange.HTTP_URI, simple("${header.CamelHttpUrl}"))
We also need to remove the PATH header because the HTTP4 component builds the final URL by concatenating the URI and PATH headers:
.removeHeader(Exchange.HTTP_PATH)
Finally, we can forward the message to the HTTP4 component. We should also set throwExceptionOnFailure=false
to forward errors to the client and httpClient.redirectsEnabled=false
to forward redirects (this can otherwise mess with some sites):
.to("http4:dummy?throwExceptionOnFailure=false&httpClient.redirectsEnabled=false");
I hope this helps anyone who is looking to implement an HTTPS proxy using Camel and was struggling like me.
Upvotes: 7