Skip to content

Commit 77d254c

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 600520c commit 77d254c

File tree

2 files changed

+53
-4
lines changed

2 files changed

+53
-4
lines changed

Diff for: client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.netty.handler.codec.DecoderResultProvider;
2222
import io.netty.handler.codec.http.HttpContent;
2323
import io.netty.handler.codec.http.HttpHeaders;
24+
import io.netty.handler.codec.http.HttpMethod;
2425
import io.netty.handler.codec.http.HttpRequest;
2526
import io.netty.handler.codec.http.HttpResponse;
2627
import io.netty.handler.codec.http.LastHttpContent;
@@ -32,6 +33,7 @@
3233
import org.asynchttpclient.netty.NettyResponseStatus;
3334
import org.asynchttpclient.netty.channel.ChannelManager;
3435
import org.asynchttpclient.netty.request.NettyRequestSender;
36+
import org.asynchttpclient.util.HttpConstants.ResponseStatusCodes;
3537

3638
import java.io.IOException;
3739
import java.net.InetSocketAddress;
@@ -43,8 +45,11 @@ public HttpHandler(AsyncHttpClientConfig config, ChannelManager channelManager,
4345
super(config, channelManager, requestSender);
4446
}
4547

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

5055
private static boolean abortAfterHandlingHeaders(AsyncHandler<?> handler, HttpHeaders responseHeaders) throws Exception {
@@ -61,7 +66,7 @@ private void handleHttpResponse(final HttpResponse response, final Channel chann
6166
HttpHeaders responseHeaders = response.headers();
6267

6368
if (!interceptors.exitAfterIntercept(channel, future, handler, response, status, responseHeaders)) {
64-
boolean abort = abortAfterHandlingStatus(handler, status) || abortAfterHandlingHeaders(handler, responseHeaders);
69+
boolean abort = abortAfterHandlingStatus(handler, httpRequest.method(), status) || abortAfterHandlingHeaders(handler, responseHeaders);
6570
if (abort) {
6671
finishUpdate(future, channel, true);
6772
}

Diff for: client/src/test/java/org/asynchttpclient/proxy/HttpsProxyTest.java

+45-1
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,27 @@
1313
package org.asynchttpclient.proxy;
1414

1515
import io.github.artsok.RepeatedIfExceptionsTest;
16+
import jakarta.servlet.ServletException;
17+
import jakarta.servlet.http.HttpServletRequest;
18+
import jakarta.servlet.http.HttpServletResponse;
19+
1620
import org.asynchttpclient.AbstractBasicTest;
1721
import org.asynchttpclient.AsyncHttpClient;
1822
import org.asynchttpclient.AsyncHttpClientConfig;
1923
import org.asynchttpclient.RequestBuilder;
2024
import org.asynchttpclient.Response;
25+
import org.asynchttpclient.proxy.ProxyServer.Builder;
2126
import org.asynchttpclient.request.body.generator.ByteArrayBodyGenerator;
2227
import org.asynchttpclient.test.EchoHandler;
28+
import org.asynchttpclient.util.HttpConstants;
2329
import org.eclipse.jetty.proxy.ConnectHandler;
30+
import org.eclipse.jetty.server.Request;
2431
import org.eclipse.jetty.server.Server;
2532
import org.eclipse.jetty.server.ServerConnector;
2633
import org.eclipse.jetty.server.handler.AbstractHandler;
2734
import org.junit.jupiter.api.AfterEach;
2835
import org.junit.jupiter.api.BeforeEach;
36+
import org.junit.jupiter.api.Test;
2937

3038
import static org.asynchttpclient.Dsl.asyncHttpClient;
3139
import static org.asynchttpclient.Dsl.config;
@@ -37,6 +45,8 @@
3745
import static org.asynchttpclient.test.TestUtils.addHttpsConnector;
3846
import static org.junit.jupiter.api.Assertions.assertEquals;
3947

48+
import java.io.IOException;
49+
4050
/**
4151
* Proxy usage tests.
4252
*/
@@ -46,7 +56,7 @@ public class HttpsProxyTest extends AbstractBasicTest {
4656

4757
@Override
4858
public AbstractHandler configureHandler() throws Exception {
49-
return new ConnectHandler();
59+
return new ProxyHandler();
5060
}
5161

5262
@Override
@@ -142,4 +152,38 @@ public void testPooledConnectionsWithProxy() throws Exception {
142152
assertEquals(200, response2.getStatusCode());
143153
}
144154
}
155+
156+
@RepeatedIfExceptionsTest(repeats = 5)
157+
public void testFailedConnectWithProxy() throws Exception {
158+
try (AsyncHttpClient asyncHttpClient = asyncHttpClient(config().setFollowRedirect(true).setUseInsecureTrustManager(true).setKeepAlive(true))) {
159+
Builder proxyServer = proxyServer("localhost", port1);
160+
proxyServer.setCustomHeaders(r -> r.getHeaders().add(ProxyHandler.HEADER_FORBIDDEN, "1"));
161+
RequestBuilder rb = get(getTargetUrl2()).setProxyServer(proxyServer);
162+
163+
Response response1 = asyncHttpClient.executeRequest(rb.build()).get();
164+
assertEquals(403, response1.getStatusCode());
165+
166+
Response response2 = asyncHttpClient.executeRequest(rb.build()).get();
167+
assertEquals(403, response2.getStatusCode());
168+
169+
Response response3 = asyncHttpClient.executeRequest(rb.build()).get();
170+
assertEquals(403, response3.getStatusCode());
171+
}
172+
}
173+
174+
public static class ProxyHandler extends ConnectHandler {
175+
final static String HEADER_FORBIDDEN = "X-REJECT-REQUEST";
176+
177+
@Override
178+
public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
179+
if (HttpConstants.Methods.CONNECT.equalsIgnoreCase(request.getMethod())) {
180+
if (request.getHeader(HEADER_FORBIDDEN) != null) {
181+
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
182+
r.setHandled(true);
183+
return;
184+
}
185+
}
186+
super.handle(s, r, request, response);
187+
}
188+
}
145189
}

0 commit comments

Comments
 (0)