Skip to content

Commit 70073f5

Browse files
committed
fix: inappropriate connection reuse when using HTTP proxy
There is an extra CONNECT request needs to send before the real request to the HTTP proxy and the 2nd request only happens if the CONNECT request succeeds. When CONNECT failed, the connection should be dropped as it's not in connected state. Signed-off-by: Jason Joo <[email protected]>
1 parent 6afba08 commit 70073f5

File tree

2 files changed

+51
-4
lines changed

2 files changed

+51
-4
lines changed

client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.asynchttpclient.netty.channel.ChannelManager;
2929
import org.asynchttpclient.netty.channel.Channels;
3030
import org.asynchttpclient.netty.request.NettyRequestSender;
31+
import org.asynchttpclient.util.HttpConstants.ResponseStatusCodes;
3132

3233
import java.io.IOException;
3334
import java.net.InetSocketAddress;
@@ -39,9 +40,12 @@ public HttpHandler(AsyncHttpClientConfig config, ChannelManager channelManager,
3940
super(config, channelManager, requestSender);
4041
}
4142

42-
private boolean abortAfterHandlingStatus(AsyncHandler<?> handler,
43+
private boolean abortAfterHandlingStatus(AsyncHandler<?> handler, HttpMethod httpMethod,
4344
NettyResponseStatus status) throws Exception {
44-
return handler.onStatusReceived(status) == State.ABORT;
45+
// For non-200 response of a CONNECT request, it's still unconnected.
46+
// We need to either close the connection or reuse it but send CONNECT request again.
47+
// The former one is easier or we have to attach more state to Channel.
48+
return handler.onStatusReceived(status) == State.ABORT || httpMethod == HttpMethod.CONNECT && status.getStatusCode() != ResponseStatusCodes.OK_200;
4549
}
4650

4751
private boolean abortAfterHandlingHeaders(AsyncHandler<?> handler,
@@ -75,7 +79,7 @@ private void handleHttpResponse(final HttpResponse response, final Channel chann
7579
HttpHeaders responseHeaders = response.headers();
7680

7781
if (!interceptors.exitAfterIntercept(channel, future, handler, response, status, responseHeaders)) {
78-
boolean abort = abortAfterHandlingStatus(handler, status) || //
82+
boolean abort = abortAfterHandlingStatus(handler, httpRequest.method(), status) || //
7983
abortAfterHandlingHeaders(handler, responseHeaders) || //
8084
abortAfterHandlingReactiveStreams(channel, future, handler);
8185

client/src/test/java/org/asynchttpclient/proxy/HttpsProxyTest.java

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
package org.asynchttpclient.proxy;
1414

1515
import org.asynchttpclient.*;
16+
import org.asynchttpclient.proxy.ProxyServer.Builder;
1617
import org.asynchttpclient.request.body.generator.ByteArrayBodyGenerator;
1718
import org.asynchttpclient.test.EchoHandler;
19+
import org.asynchttpclient.util.HttpConstants;
1820
import org.eclipse.jetty.proxy.ConnectHandler;
1921
import org.eclipse.jetty.server.Server;
2022
import org.eclipse.jetty.server.ServerConnector;
@@ -29,6 +31,12 @@
2931
import static org.asynchttpclient.test.TestUtils.addHttpsConnector;
3032
import static org.testng.Assert.assertEquals;
3133

34+
import java.io.IOException;
35+
36+
import javax.servlet.ServletException;
37+
import javax.servlet.http.HttpServletRequest;
38+
import javax.servlet.http.HttpServletResponse;
39+
3240
/**
3341
* Proxy usage tests.
3442
*/
@@ -37,7 +45,7 @@ public class HttpsProxyTest extends AbstractBasicTest {
3745
private Server server2;
3846

3947
public AbstractHandler configureHandler() throws Exception {
40-
return new ConnectHandler();
48+
return new ProxyHandler();
4149
}
4250

4351
@BeforeClass(alwaysRun = true)
@@ -129,4 +137,39 @@ public void testPooledConnectionsWithProxy() throws Exception {
129137
assertEquals(r2.getStatusCode(), 200);
130138
}
131139
}
140+
141+
@Test
142+
public void testFailedConnectWithProxy() throws Exception {
143+
try (AsyncHttpClient asyncHttpClient = asyncHttpClient(config().setFollowRedirect(true).setUseInsecureTrustManager(true).setKeepAlive(true))) {
144+
Builder proxyServer = proxyServer("localhost", port1);
145+
proxyServer.setCustomHeaders(r -> r.getHeaders().add(ProxyHandler.HEADER_FORBIDDEN, "1"));
146+
RequestBuilder rb = get(getTargetUrl2()).setProxyServer(proxyServer);
147+
148+
Response response1 = asyncHttpClient.executeRequest(rb.build()).get();
149+
assertEquals(403, response1.getStatusCode());
150+
151+
Response response2 = asyncHttpClient.executeRequest(rb.build()).get();
152+
assertEquals(403, response2.getStatusCode());
153+
154+
Response response3 = asyncHttpClient.executeRequest(rb.build()).get();
155+
assertEquals(403, response3.getStatusCode());
156+
}
157+
}
158+
159+
public static class ProxyHandler extends ConnectHandler {
160+
final static String HEADER_FORBIDDEN = "X-REJECT-REQUEST";
161+
162+
@Override
163+
public void handle(String s, org.eclipse.jetty.server.Request r, HttpServletRequest request,
164+
HttpServletResponse response) throws ServletException, IOException {
165+
if (HttpConstants.Methods.CONNECT.equalsIgnoreCase(request.getMethod())) {
166+
if (request.getHeader(HEADER_FORBIDDEN) != null) {
167+
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
168+
r.setHandled(true);
169+
return;
170+
}
171+
}
172+
super.handle(s, r, request, response);
173+
}
174+
}
132175
}

0 commit comments

Comments
 (0)