Description
The connection pool logic in NettyAsyncHttpProvider does not appear to be working when proxying over SSL.
URI connectionKeyUri = useProxy? proxyServer.getURI() : uri;
channel = lookupInCache(connectionKeyUri, request.getConnectionPoolKeyStrategy());
What appears to be happening is that request #1 creates a new connection keyed to the proxy address and properly issues an HTTP CONNECT for the first host. Request #2 targets a different host via the same proxy, pulls the cached connection since the proxy address is the same but then ends up sending the request to wrong host since that connection is still connected to the first host.
I can hack up NettyAsyncHttpProvider.java if needed by creating a fake URI in the format : scheme://targetHost.proxyHost:proxyPort/ when ssl + proxy is used. The proper solution however would seem to require changing the ConnectionPoolKeyStrategy interface since a URI alone is not enough to correctly compute the pool key.
The following code will re-create the problem.
Builder builder = new AsyncHttpClientConfig.Builder()
.setAllowPoolingConnection(true);
.setAllowSslConnectionPool(true);
try {
// provide our own SSLContext that accepts all certicates
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[] { new X509ExtendedTrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
}
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {
}
} }, null);
builder.setSSLContext(sslContext);
} catch (Exception e) {
LOGGER.error("SSL not supported by JVM!", e);
}
AsyncHttpClient httpClient = new AsyncHttpClient(builder.build());
RequestBuilder requestBuilder1 = new RequestBuilder("GET")
.setUrl("https://www.google.com/");
.setProxyServer(new ProxyServer(ProxyServer.Protocol.HTTPS, args[0], Integer.parseInt(args[1])));
httpClient.executeRequest(requestBuilder1.build(), new AsyncHandler<Response>() {
private final Response.ResponseBuilder builder = new Response.ResponseBuilder();
public void onThrowable(Throwable t) {
}
public AsyncHandler.STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception {
builder.accumulate(bodyPart);
return STATE.CONTINUE;
}
public AsyncHandler.STATE onStatusReceived(HttpResponseStatus status) throws Exception {
builder.accumulate(status);
return STATE.CONTINUE;
}
public AsyncHandler.STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception {
builder.accumulate(headers);
return STATE.CONTINUE;
}
public Response onCompleted() throws Exception {
Response response = builder.build();
System.out.println(response.getStatusCode());
System.out.println(response.getResponseBody());
return response;
}
});
RequestBuilder requestBuilder2 = new RequestBuilder("GET")
.setUrl("https://www.facebook.com/");
.setProxyServer(new ProxyServer(ProxyServer.Protocol.HTTPS, args[0], Integer.parseInt(args[1])));
httpClient.executeRequest(requestBuilder2.build(), new AsyncHandler<Response>() {
private final Response.ResponseBuilder builder = new Response.ResponseBuilder();
public void onThrowable(Throwable t) {
}
public AsyncHandler.STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception {
builder.accumulate(bodyPart);
return STATE.CONTINUE;
}
public AsyncHandler.STATE onStatusReceived(HttpResponseStatus status) throws Exception {
builder.accumulate(status);
return STATE.CONTINUE;
}
public AsyncHandler.STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception {
builder.accumulate(headers);
return STATE.CONTINUE;
}
public Response onCompleted() throws Exception {
Response response = builder.build();
System.out.println(response.getStatusCode());
System.out.println(response.getResponseBody());
return response;
}
});