diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index f4538d3c7..000000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-# To get started with Dependabot version updates, you'll need to specify which
-# package ecosystems to update and where the package manifests are located.
-# Please see the documentation for all configuration options:
-# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
-
-version: 2
-updates:
- - package-ecosystem: "maven"
- directories:
- - "/"
- schedule:
- interval: "daily"
- - package-ecosystem: "github-actions"
- directories:
- - "/"
- schedule:
- interval: "daily"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4a462dc99..b175fa865 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -37,7 +37,7 @@ jobs:
}]
- name: Import GPG
- uses: crazy-max/ghaction-import-gpg@v6.2.0
+ uses: crazy-max/ghaction-import-gpg@v6.3.0
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
diff --git a/README.md b/README.md
index 4ae651b75..0272134ed 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ Maven:
org.asynchttpclient
async-http-client
- 3.0.1
+ 3.0.2
```
@@ -28,7 +28,7 @@ Maven:
Gradle:
```groovy
dependencies {
- implementation 'org.asynchttpclient:async-http-client:3.0.1'
+ implementation 'org.asynchttpclient:async-http-client:3.0.2'
}
```
diff --git a/client/pom.xml b/client/pom.xml
index 58dcd0aad..749a98ddb 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -19,7 +19,7 @@
org.asynchttpclient
async-http-client-project
- 3.0.1
+ 3.0.2
4.0.0
@@ -31,7 +31,7 @@
org.asynchttpclient.client
11.0.24
- 10.1.33
+ 10.1.39
2.18.0
4.11.0
3.0
diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java b/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java
index 06ec46a2b..99a23c7e9 100755
--- a/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java
+++ b/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java
@@ -21,6 +21,7 @@
import io.netty.handler.codec.DecoderResultProvider;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.LastHttpContent;
@@ -32,6 +33,7 @@
import org.asynchttpclient.netty.NettyResponseStatus;
import org.asynchttpclient.netty.channel.ChannelManager;
import org.asynchttpclient.netty.request.NettyRequestSender;
+import org.asynchttpclient.util.HttpConstants.ResponseStatusCodes;
import java.io.IOException;
import java.net.InetSocketAddress;
@@ -43,8 +45,11 @@ public HttpHandler(AsyncHttpClientConfig config, ChannelManager channelManager,
super(config, channelManager, requestSender);
}
- private static boolean abortAfterHandlingStatus(AsyncHandler> handler, NettyResponseStatus status) throws Exception {
- return handler.onStatusReceived(status) == State.ABORT;
+ private static boolean abortAfterHandlingStatus(AsyncHandler> handler, HttpMethod httpMethod, NettyResponseStatus status) throws Exception {
+ // For non-200 response of a CONNECT request, it's still unconnected.
+ // We need to either close the connection or reuse it but send CONNECT request again.
+ // The former one is easier or we have to attach more state to Channel.
+ return handler.onStatusReceived(status) == State.ABORT || httpMethod == HttpMethod.CONNECT && status.getStatusCode() != ResponseStatusCodes.OK_200;
}
private static boolean abortAfterHandlingHeaders(AsyncHandler> handler, HttpHeaders responseHeaders) throws Exception {
@@ -61,7 +66,7 @@ private void handleHttpResponse(final HttpResponse response, final Channel chann
HttpHeaders responseHeaders = response.headers();
if (!interceptors.exitAfterIntercept(channel, future, handler, response, status, responseHeaders)) {
- boolean abort = abortAfterHandlingStatus(handler, status) || abortAfterHandlingHeaders(handler, responseHeaders);
+ boolean abort = abortAfterHandlingStatus(handler, httpRequest.method(), status) || abortAfterHandlingHeaders(handler, responseHeaders);
if (abort) {
finishUpdate(future, channel, true);
}
diff --git a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java
index 9fff868b2..b66dd713d 100755
--- a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java
+++ b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java
@@ -97,6 +97,13 @@ public NettyRequestSender(AsyncHttpClientConfig config, ChannelManager channelMa
requestFactory = new NettyRequestFactory(config);
}
+ // needConnect returns true if the request is secure/websocket and a HTTP proxy is set
+ private boolean needConnect(final Request request, final ProxyServer proxyServer) {
+ return proxyServer != null
+ && proxyServer.getProxyType().isHttp()
+ && (request.getUri().isSecured() || request.getUri().isWebSocket());
+ }
+
public ListenableFuture sendRequest(final Request request, final AsyncHandler asyncHandler, NettyResponseFuture future) {
if (isClosed()) {
throw new IllegalStateException("Closed");
@@ -106,9 +113,7 @@ public ListenableFuture sendRequest(final Request request, final AsyncHan
ProxyServer proxyServer = getProxyServer(config, request);
// WebSockets use connect tunneling to work with proxies
- if (proxyServer != null && proxyServer.getProxyType().isHttp() &&
- (request.getUri().isSecured() || request.getUri().isWebSocket()) &&
- !isConnectAlreadyDone(request, future)) {
+ if (needConnect(request, proxyServer) && !isConnectAlreadyDone(request, future)) {
// Proxy with HTTPS or WebSocket: CONNECT for sure
if (future != null && future.isConnectAllowed()) {
// Perform CONNECT
@@ -125,6 +130,8 @@ public ListenableFuture sendRequest(final Request request, final AsyncHan
private static boolean isConnectAlreadyDone(Request request, NettyResponseFuture> future) {
return future != null
+ // If the channel can't be reused or closed, a CONNECT is still required
+ && future.isReuseChannel() && Channels.isChannelActive(future.channel())
&& future.getNettyRequest() != null
&& future.getNettyRequest().getHttpRequest().method() == HttpMethod.CONNECT
&& !request.getMethod().equals(CONNECT);
@@ -137,11 +144,19 @@ private static boolean isConnectAlreadyDone(Request request, NettyResponseFuture
*/
private ListenableFuture sendRequestWithCertainForceConnect(Request request, AsyncHandler asyncHandler, NettyResponseFuture future,
ProxyServer proxyServer, boolean performConnectRequest) {
- NettyResponseFuture newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, proxyServer, performConnectRequest);
Channel channel = getOpenChannel(future, request, proxyServer, asyncHandler);
- return Channels.isChannelActive(channel)
- ? sendRequestWithOpenChannel(newFuture, asyncHandler, channel)
- : sendRequestWithNewChannel(request, proxyServer, newFuture, asyncHandler);
+ if (Channels.isChannelActive(channel)) {
+ NettyResponseFuture newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future,
+ proxyServer, performConnectRequest);
+ return sendRequestWithOpenChannel(newFuture, asyncHandler, channel);
+ } else {
+ // A new channel is not expected when performConnectRequest is false. We need to
+ // revisit the condition of sending
+ // the CONNECT request to the new channel.
+ NettyResponseFuture newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future,
+ proxyServer, needConnect(request, proxyServer));
+ return sendRequestWithNewChannel(request, proxyServer, newFuture, asyncHandler);
+ }
}
/**
diff --git a/client/src/test/java/org/asynchttpclient/AutomaticDecompressionTest.java b/client/src/test/java/org/asynchttpclient/AutomaticDecompressionTest.java
index dfd0a9446..8f57ffb88 100644
--- a/client/src/test/java/org/asynchttpclient/AutomaticDecompressionTest.java
+++ b/client/src/test/java/org/asynchttpclient/AutomaticDecompressionTest.java
@@ -22,6 +22,7 @@
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
+import io.github.nettyplus.leakdetector.junit.NettyLeakDetectorExtension;
import io.netty.handler.codec.compression.Brotli;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@@ -35,11 +36,13 @@
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
+import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
+@ExtendWith(NettyLeakDetectorExtension.class)
public class AutomaticDecompressionTest {
- private static final String UNCOMPRESSED_PAYLOAD = "a".repeat(500);
+ private static final String UNCOMPRESSED_PAYLOAD = "a".repeat(50_000);
private static HttpServer HTTP_SERVER;
diff --git a/client/src/test/java/org/asynchttpclient/proxy/HttpsProxyTest.java b/client/src/test/java/org/asynchttpclient/proxy/HttpsProxyTest.java
index 6c4109aec..9bd5ca911 100644
--- a/client/src/test/java/org/asynchttpclient/proxy/HttpsProxyTest.java
+++ b/client/src/test/java/org/asynchttpclient/proxy/HttpsProxyTest.java
@@ -13,14 +13,22 @@
package org.asynchttpclient.proxy;
import io.github.artsok.RepeatedIfExceptionsTest;
+import io.netty.handler.codec.http.DefaultHttpHeaders;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
import org.asynchttpclient.AbstractBasicTest;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.AsyncHttpClientConfig;
import org.asynchttpclient.RequestBuilder;
import org.asynchttpclient.Response;
+import org.asynchttpclient.proxy.ProxyServer.Builder;
import org.asynchttpclient.request.body.generator.ByteArrayBodyGenerator;
import org.asynchttpclient.test.EchoHandler;
+import org.asynchttpclient.util.HttpConstants;
import org.eclipse.jetty.proxy.ConnectHandler;
+import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
@@ -36,6 +44,10 @@
import static org.asynchttpclient.test.TestUtils.addHttpConnector;
import static org.asynchttpclient.test.TestUtils.addHttpsConnector;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
/**
* Proxy usage tests.
@@ -46,7 +58,7 @@ public class HttpsProxyTest extends AbstractBasicTest {
@Override
public AbstractHandler configureHandler() throws Exception {
- return new ConnectHandler();
+ return new ProxyHandler();
}
@Override
@@ -142,4 +154,61 @@ public void testPooledConnectionsWithProxy() throws Exception {
assertEquals(200, response2.getStatusCode());
}
}
+
+ @RepeatedIfExceptionsTest(repeats = 5)
+ public void testFailedConnectWithProxy() throws Exception {
+ try (AsyncHttpClient asyncHttpClient = asyncHttpClient(config().setFollowRedirect(true).setUseInsecureTrustManager(true).setKeepAlive(true))) {
+ Builder proxyServer = proxyServer("localhost", port1);
+ proxyServer.setCustomHeaders(r -> new DefaultHttpHeaders().set(ProxyHandler.HEADER_FORBIDDEN, "1"));
+ RequestBuilder rb = get(getTargetUrl2()).setProxyServer(proxyServer);
+
+ Response response1 = asyncHttpClient.executeRequest(rb.build()).get();
+ assertEquals(403, response1.getStatusCode());
+
+ Response response2 = asyncHttpClient.executeRequest(rb.build()).get();
+ assertEquals(403, response2.getStatusCode());
+
+ Response response3 = asyncHttpClient.executeRequest(rb.build()).get();
+ assertEquals(403, response3.getStatusCode());
+ }
+ }
+
+ @RepeatedIfExceptionsTest(repeats = 5)
+ public void testClosedConnectionWithProxy() throws Exception {
+ try (AsyncHttpClient asyncHttpClient = asyncHttpClient(
+ config().setFollowRedirect(true).setUseInsecureTrustManager(true).setKeepAlive(true))) {
+ Builder proxyServer = proxyServer("localhost", port1);
+ proxyServer.setCustomHeaders(r -> new DefaultHttpHeaders().set(ProxyHandler.HEADER_FORBIDDEN, "2"));
+ RequestBuilder rb = get(getTargetUrl2()).setProxyServer(proxyServer);
+
+ assertThrowsExactly(ExecutionException.class, () -> asyncHttpClient.executeRequest(rb.build()).get());
+ assertThrowsExactly(ExecutionException.class, () -> asyncHttpClient.executeRequest(rb.build()).get());
+ assertThrowsExactly(ExecutionException.class, () -> asyncHttpClient.executeRequest(rb.build()).get());
+ }
+ }
+
+ public static class ProxyHandler extends ConnectHandler {
+ final static String HEADER_FORBIDDEN = "X-REJECT-REQUEST";
+
+ @Override
+ public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ if (HttpConstants.Methods.CONNECT.equalsIgnoreCase(request.getMethod())) {
+ String headerValue = request.getHeader(HEADER_FORBIDDEN);
+ if (headerValue == null) {
+ headerValue = "";
+ }
+ switch (headerValue) {
+ case "1":
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ r.setHandled(true);
+ return;
+ case "2":
+ r.getHttpChannel().getConnection().close();
+ r.setHandled(true);
+ return;
+ }
+ }
+ super.handle(s, r, request, response);
+ }
+ }
}
diff --git a/pom.xml b/pom.xml
index d02f7c7ee..70d09ac53 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
org.asynchttpclient
async-http-client-project
- 3.0.1
+ 3.0.2
pom
AHC/Project
@@ -45,14 +45,14 @@
11
UTF-8
- 4.1.115.Final
- 0.0.25.Final
- 1.17.0
+ 4.1.119.Final
+ 0.0.26.Final
+ 1.18.0
2.0.16
- 1.5.6-8
+ 1.5.7-2
2.0.1
- 1.5.12
- 26.0.1
+ 1.5.18
+ 26.0.2
@@ -105,14 +105,14 @@
org.junit
junit-bom
- 5.11.3
+ 5.11.4
pom
import
io.github.nettyplus
netty-leak-detector-junit-extension
- 0.0.5
+ 0.0.6
@@ -293,7 +293,7 @@
org.apache.maven.plugins
maven-compiler-plugin
- 3.13.0
+ 3.14.0
11
11
@@ -327,7 +327,7 @@
com.uber.nullaway
nullaway
- 0.12.1
+ 0.12.6