diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 6a59bde6c..2586cf3c6 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -5,34 +5,50 @@ on: - cron: '0 12 * * *' jobs: + Verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Grant Permission + run: chmod +x ./mvnw + - uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: '11' + - name: Verify + run: ./mvnw -B -ntp clean verify -DskipTests -Dgpg.skip=true + RunOnLinux: runs-on: ubuntu-latest + needs: Verify steps: - uses: actions/checkout@v4 - name: Grant Permission - run: sudo chmod +x ./mvnw + run: chmod +x ./mvnw - uses: actions/setup-java@v4 with: distribution: 'corretto' java-version: '11' - name: Run Tests - run: ./mvnw -B -ntp clean test + run: ./mvnw -B -ntp test RunOnMacOs: runs-on: macos-latest + needs: Verify steps: - uses: actions/checkout@v4 - name: Grant Permission - run: sudo chmod +x ./mvnw + run: chmod +x ./mvnw - uses: actions/setup-java@v4 with: distribution: 'corretto' java-version: '11' - name: Run Tests - run: ./mvnw -B -ntp clean test + run: ./mvnw -B -ntp test RunOnWindows: runs-on: windows-latest + needs: Verify steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 @@ -40,4 +56,4 @@ jobs: distribution: 'corretto' java-version: '11' - name: Run Tests - run: ./mvnw.cmd -B -ntp clean test + run: ./mvnw.cmd -B -ntp test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 51dc38f90..b175fa865 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: run: rm -f /home/runner/.m2/settings.xml - name: Maven Settings - uses: s4u/maven-settings-action@v2.2.0 + uses: s4u/maven-settings-action@v3.1.0 with: servers: | [{ @@ -37,7 +37,7 @@ jobs: }] - name: Import GPG - uses: crazy-max/ghaction-import-gpg@v5.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/LICENSE.txt b/LICENSE.txt index d8e4ed073..85a16d3d0 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ - Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/LICENSES/LICENSE.zstd-jni.txt b/LICENSES/LICENSE.zstd-jni.txt new file mode 100644 index 000000000..66abb8ae7 --- /dev/null +++ b/LICENSES/LICENSE.zstd-jni.txt @@ -0,0 +1,26 @@ +Zstd-jni: JNI bindings to Zstd Library + +Copyright (c) 2015-present, Luben Karavelov/ All rights reserved. + +BSD License + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 111dc9643..0272134ed 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Maven: org.asynchttpclient async-http-client - 3.0.0.Beta3 + 3.0.2 ``` @@ -28,24 +28,10 @@ Maven: Gradle: ```groovy dependencies { - implementation 'org.asynchttpclient:async-http-client:3.0.0.Beta3' + implementation 'org.asynchttpclient:async-http-client:3.0.2' } ``` -## Version - -AHC doesn't use SEMVER, and won't. - -* MAJOR = huge refactoring -* MINOR = new features and minor API changes, upgrading should require 1 hour of work to adapt sources -* FIX = no API change, just bug fixes, only those are source and binary compatible with same minor version - -Check CHANGES.md for migration path between versions. - -## Basics - -Feel free to check the [Javadoc](http://www.javadoc.io/doc/org.asynchttpclient/async-http-client/) or the code for more information. - ### Dsl Import the Dsl helpers to use convenient methods to bootstrap components: @@ -112,7 +98,7 @@ This body can be of type: * `String` * `java.nio.ByteBuffer` * `java.io.InputStream` -* `Publisher` +* `Publisher` * `org.asynchttpclient.request.body.generator.BodyGenerator` `BodyGenerator` is a generic abstraction that let you create request bodies on the fly. @@ -244,75 +230,34 @@ Async Http Client also supports WebSocket. You need to pass a `WebSocketUpgradeHandler` where you would register a `WebSocketListener`. ```java -WebSocket websocket=c.prepareGet("ws://demos.kaazing.com/echo") +WebSocket websocket = c.prepareGet("ws://demos.kaazing.com/echo") .execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener( - new WebSocketListener(){ - - @Override - public void onOpen(WebSocket websocket){ - websocket.sendTextFrame("...").sendTextFrame("..."); - } - - @Override - public void onClose(WebSocket websocket) { - // ... - } - - @Override - public void onTextFrame(String payload,boolean finalFragment,int rsv){ - System.out.println(payload); - } - - @Override - public void onError(Throwable t){ - t.printStackTrace(); - } - }).build()).get(); + new WebSocketListener() { + + @Override + public void onOpen(WebSocket websocket) { + websocket.sendTextFrame("...").sendTextFrame("..."); + } + + @Override + public void onClose(WebSocket websocket) { + // ... + } + + @Override + public void onTextFrame(String payload, boolean finalFragment, int rsv) { + System.out.println(payload); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + } + }).build()).get(); ``` -## WebDAV - -AsyncHttpClient has build in support for the WebDAV protocol. -The API can be used the same way normal HTTP request are made: - -```java - Request mkcolRequest=new RequestBuilder("MKCOL").setUrl("http://host:port/folder1").build(); - Response response=c.executeRequest(mkcolRequest).get(); -``` - -or - -```java - Request propFindRequest=new RequestBuilder("PROPFIND").setUrl("http://host:port").build(); - Response response=c.executeRequest(propFindRequest,new AsyncHandler() { - // ... - }).get(); -``` - -## More - -You can find more information on Jean-François Arcand's blog. Jean-François is the original author of this library. -Code is sometimes not up-to-date but gives a pretty good idea of advanced features. - -* http://web.archive.org/web/20111224171448/http://jfarcand.wordpress.com/2011/01/12/going-asynchronous-using-asynchttpclient-for-dummies/ -* http://web.archive.org/web/20111224171241/http://jfarcand.wordpress.com/2010/12/21/going-asynchronous-using-asynchttpclient-the-basic/ -* http://web.archive.org/web/20111224162752/http://jfarcand.wordpress.com/2011/01/04/going-asynchronous-using-asynchttpclient-the-complex/ -* http://web.archive.org/web/20120218183108/http://jfarcand.wordpress.com/2011/12/21/writing-websocket-clients-using-asynchttpclient/ - ## User Group Keep up to date on the library development by joining the Asynchronous HTTP Client discussion group [GitHub Discussions](https://github.com/AsyncHttpClient/async-http-client/discussions) - -## Contributing - -Of course, Pull Requests are welcome. - -Here are the few rules we'd like you to respect if you do so: - -* Only edit the code related to the suggested change, so DON'T automatically format the classes you've edited. -* Use IntelliJ default formatting rules. -* Regarding licensing: - * You must be the original author of the code you suggest. - * You must give the copyright to "the AsyncHttpClient Project" diff --git a/client/pom.xml b/client/pom.xml index 7a7efb499..749a98ddb 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -19,7 +19,7 @@ org.asynchttpclient async-http-client-project - 3.0.0.Beta3 + 3.0.2 4.0.0 @@ -30,14 +30,22 @@ org.asynchttpclient.client - 11.0.16 - 10.1.19 - 2.11.0 + 11.0.24 + 10.1.39 + 2.18.0 4.11.0 - 2.2 - 2.0.2 + 3.0 + 2.1.0 + + + hyperxpro + Aayush Atharva + aayush@shieldblaze.com + + + diff --git a/client/src/main/java/org/asynchttpclient/AsyncHandler.java b/client/src/main/java/org/asynchttpclient/AsyncHandler.java index a912ad9c5..22451fe09 100644 --- a/client/src/main/java/org/asynchttpclient/AsyncHandler.java +++ b/client/src/main/java/org/asynchttpclient/AsyncHandler.java @@ -116,7 +116,8 @@ default State onTrailingHeadersReceived(HttpHeaders headers) throws Exception { * @return T Value that will be returned by the associated {@link Future} * @throws Exception if something wrong happens */ - @Nullable T onCompleted() throws Exception; + @Nullable + T onCompleted() throws Exception; /** * Notify the callback before hostname resolution diff --git a/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index 484cd0029..954628b3d 100644 --- a/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -160,7 +160,8 @@ public interface AsyncHttpClientConfig { * @return the {@link ThreadFactory} an {@link AsyncHttpClient} use for handling asynchronous response. If no {@link ThreadFactory} has been explicitly * provided, this method will return {@code null} */ - @Nullable ThreadFactory getThreadFactory(); + @Nullable + ThreadFactory getThreadFactory(); /** * An instance of {@link ProxyServer} used by an {@link AsyncHttpClient} @@ -174,14 +175,16 @@ public interface AsyncHttpClientConfig { * * @return an instance of {@link SslContext} used for SSL connection. */ - @Nullable SslContext getSslContext(); + @Nullable + SslContext getSslContext(); /** * Return the current {@link Realm} * * @return the current {@link Realm} */ - @Nullable Realm getRealm(); + @Nullable + Realm getRealm(); /** * Return the list of {@link RequestFilter} @@ -260,12 +263,14 @@ public interface AsyncHttpClientConfig { /** * @return the array of enabled protocols */ - @Nullable String[] getEnabledProtocols(); + @Nullable + String[] getEnabledProtocols(); /** * @return the array of enabled cipher suites */ - @Nullable String[] getEnabledCipherSuites(); + @Nullable + String[] getEnabledCipherSuites(); /** * @return if insecure cipher suites must be filtered out (only used when not explicitly passing enabled cipher suites) @@ -294,7 +299,8 @@ public interface AsyncHttpClientConfig { int getHandshakeTimeout(); - @Nullable SslEngineFactory getSslEngineFactory(); + @Nullable + SslEngineFactory getSslEngineFactory(); int getChunkedFileChunkSize(); @@ -310,23 +316,29 @@ public interface AsyncHttpClientConfig { Map, Object> getChannelOptions(); - @Nullable EventLoopGroup getEventLoopGroup(); + @Nullable + EventLoopGroup getEventLoopGroup(); boolean isUseNativeTransport(); boolean isUseOnlyEpollNativeTransport(); - @Nullable Consumer getHttpAdditionalChannelInitializer(); + @Nullable + Consumer getHttpAdditionalChannelInitializer(); - @Nullable Consumer getWsAdditionalChannelInitializer(); + @Nullable + Consumer getWsAdditionalChannelInitializer(); ResponseBodyPartFactory getResponseBodyPartFactory(); - @Nullable ChannelPool getChannelPool(); + @Nullable + ChannelPool getChannelPool(); - @Nullable ConnectionSemaphoreFactory getConnectionSemaphoreFactory(); + @Nullable + ConnectionSemaphoreFactory getConnectionSemaphoreFactory(); - @Nullable Timer getNettyTimer(); + @Nullable + Timer getNettyTimer(); /** * @return the duration between tick of {@link HashedWheelTimer} @@ -358,10 +370,18 @@ public interface AsyncHttpClientConfig { int getSoRcvBuf(); - @Nullable ByteBufAllocator getAllocator(); + @Nullable + ByteBufAllocator getAllocator(); int getIoThreadsCount(); + /** + * Indicates whether the Authorization header should be stripped during redirects to a different domain. + * + * @return true if the Authorization header should be stripped, false otherwise. + */ + boolean isStripAuthorizationOnRedirect(); + enum ResponseBodyPartFactory { EAGER { diff --git a/client/src/main/java/org/asynchttpclient/BoundRequestBuilder.java b/client/src/main/java/org/asynchttpclient/BoundRequestBuilder.java index c2e2bce29..99b3cc5d0 100644 --- a/client/src/main/java/org/asynchttpclient/BoundRequestBuilder.java +++ b/client/src/main/java/org/asynchttpclient/BoundRequestBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/ClientStats.java b/client/src/main/java/org/asynchttpclient/ClientStats.java index 7d450ad96..eef529221 100644 --- a/client/src/main/java/org/asynchttpclient/ClientStats.java +++ b/client/src/main/java/org/asynchttpclient/ClientStats.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java index fb5dad6ff..3b417a5a3 100644 --- a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java +++ b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java @@ -24,8 +24,8 @@ import org.asynchttpclient.channel.ChannelPool; import org.asynchttpclient.cookie.CookieEvictionTask; import org.asynchttpclient.cookie.CookieStore; -import org.asynchttpclient.filter.FilterContext; import org.asynchttpclient.exception.FilterException; +import org.asynchttpclient.filter.FilterContext; import org.asynchttpclient.filter.RequestFilter; import org.asynchttpclient.handler.resumable.ResumableAsyncHandler; import org.asynchttpclient.netty.channel.ChannelManager; @@ -235,7 +235,7 @@ public ListenableFuture executeRequest(Request request, AsyncHandler h if (!cookies.isEmpty()) { RequestBuilder requestBuilder = request.toBuilder(); for (Cookie cookie : cookies) { - requestBuilder.addOrReplaceCookie(cookie); + requestBuilder.addCookieIfUnset(cookie); } request = requestBuilder.build(); } diff --git a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java index 0357592bd..1c7dbf37f 100644 --- a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java +++ b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java @@ -127,6 +127,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig { private final boolean keepEncodingHeader; private final ProxyServerSelector proxyServerSelector; private final boolean validateResponseHeaders; + private final boolean stripAuthorizationOnRedirect; // websockets private final boolean aggregateWebSocketFrameFragments; @@ -219,6 +220,7 @@ private DefaultAsyncHttpClientConfig(// http boolean validateResponseHeaders, boolean aggregateWebSocketFrameFragments, boolean enablewebSocketCompression, + boolean stripAuthorizationOnRedirect, // timeouts Duration connectTimeout, @@ -307,6 +309,7 @@ private DefaultAsyncHttpClientConfig(// http this.keepEncodingHeader = keepEncodingHeader; this.proxyServerSelector = proxyServerSelector; this.validateResponseHeaders = validateResponseHeaders; + this.stripAuthorizationOnRedirect = stripAuthorizationOnRedirect; // websocket this.aggregateWebSocketFrameFragments = aggregateWebSocketFrameFragments; @@ -564,6 +567,11 @@ public boolean isValidateResponseHeaders() { return validateResponseHeaders; } + @Override + public boolean isStripAuthorizationOnRedirect() { + return stripAuthorizationOnRedirect; + } + // ssl @Override public boolean isUseOpenSsl() { @@ -800,6 +808,7 @@ public static class Builder { private boolean useProxySelector = defaultUseProxySelector(); private boolean useProxyProperties = defaultUseProxyProperties(); private boolean validateResponseHeaders = defaultValidateResponseHeaders(); + private boolean stripAuthorizationOnRedirect = false; // default value // websocket private boolean aggregateWebSocketFrameFragments = defaultAggregateWebSocketFrameFragments(); @@ -890,6 +899,8 @@ public Builder(AsyncHttpClientConfig config) { disableZeroCopy = config.isDisableZeroCopy(); keepEncodingHeader = config.isKeepEncodingHeader(); proxyServerSelector = config.getProxyServerSelector(); + validateResponseHeaders = config.isValidateResponseHeaders(); + stripAuthorizationOnRedirect = config.isStripAuthorizationOnRedirect(); // websocket aggregateWebSocketFrameFragments = config.isAggregateWebSocketFrameFragments(); @@ -907,15 +918,19 @@ public Builder(AsyncHttpClientConfig config) { // keep-alive keepAlive = config.isKeepAlive(); pooledConnectionIdleTimeout = config.getPooledConnectionIdleTimeout(); + connectionPoolCleanerPeriod = config.getConnectionPoolCleanerPeriod(); connectionTtl = config.getConnectionTtl(); maxConnections = config.getMaxConnections(); maxConnectionsPerHost = config.getMaxConnectionsPerHost(); channelPool = config.getChannelPool(); connectionSemaphoreFactory = config.getConnectionSemaphoreFactory(); keepAliveStrategy = config.getKeepAliveStrategy(); + acquireFreeChannelTimeout = config.getAcquireFreeChannelTimeout(); // ssl + useOpenSsl = config.isUseOpenSsl(); useInsecureTrustManager = config.isUseInsecureTrustManager(); + disableHttpsEndpointIdentificationAlgorithm = config.isDisableHttpsEndpointIdentificationAlgorithm(); handshakeTimeout = config.getHandshakeTimeout(); enabledProtocols = config.getEnabledProtocols(); enabledCipherSuites = config.getEnabledCipherSuites(); @@ -930,6 +945,10 @@ public Builder(AsyncHttpClientConfig config) { responseFilters.addAll(config.getResponseFilters()); ioExceptionFilters.addAll(config.getIoExceptionFilters()); + // cookie store + cookieStore = config.getCookieStore(); + expiredCookieEvictionDelay = config.expiredCookieEvictionDelay(); + // tuning tcpNoDelay = config.isTcpNoDelay(); soReuseAddress = config.isSoReuseAddress(); @@ -943,6 +962,7 @@ public Builder(AsyncHttpClientConfig config) { httpClientCodecMaxInitialLineLength = config.getHttpClientCodecMaxInitialLineLength(); httpClientCodecMaxHeaderSize = config.getHttpClientCodecMaxHeaderSize(); httpClientCodecMaxChunkSize = config.getHttpClientCodecMaxChunkSize(); + httpClientCodecInitialBufferSize = config.getHttpClientCodecInitialBufferSize(); chunkedFileChunkSize = config.getChunkedFileChunkSize(); channelOptions.putAll(config.getChannelOptions()); eventLoopGroup = config.getEventLoopGroup(); @@ -1069,6 +1089,11 @@ public Builder setUseProxyProperties(boolean useProxyProperties) { return this; } + public Builder setStripAuthorizationOnRedirect(boolean value) { + stripAuthorizationOnRedirect = value; + return this; + } + // websocket public Builder setAggregateWebSocketFrameFragments(boolean aggregateWebSocketFrameFragments) { this.aggregateWebSocketFrameFragments = aggregateWebSocketFrameFragments; @@ -1434,6 +1459,7 @@ public DefaultAsyncHttpClientConfig build() { validateResponseHeaders, aggregateWebSocketFrameFragments, enablewebSocketCompression, + stripAuthorizationOnRedirect, connectTimeout, requestTimeout, readTimeout, diff --git a/client/src/main/java/org/asynchttpclient/DefaultRequest.java b/client/src/main/java/org/asynchttpclient/DefaultRequest.java index 4170c33e2..09c615d2a 100644 --- a/client/src/main/java/org/asynchttpclient/DefaultRequest.java +++ b/client/src/main/java/org/asynchttpclient/DefaultRequest.java @@ -15,6 +15,7 @@ */ package org.asynchttpclient; +import io.netty.buffer.ByteBuf; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.resolver.NameResolver; @@ -51,6 +52,7 @@ public class DefaultRequest implements Request { private final @Nullable List compositeByteData; private final @Nullable String stringData; private final @Nullable ByteBuffer byteBufferData; + private final @Nullable ByteBuf byteBufData; private final @Nullable InputStream streamData; private final @Nullable BodyGenerator bodyGenerator; private final List formParams; @@ -79,6 +81,7 @@ public DefaultRequest(String method, @Nullable List compositeByteData, @Nullable String stringData, @Nullable ByteBuffer byteBufferData, + @Nullable ByteBuf byteBufData, @Nullable InputStream streamData, @Nullable BodyGenerator bodyGenerator, List formParams, @@ -104,6 +107,7 @@ public DefaultRequest(String method, this.compositeByteData = compositeByteData; this.stringData = stringData; this.byteBufferData = byteBufferData; + this.byteBufData = byteBufData; this.streamData = streamData; this.bodyGenerator = bodyGenerator; this.formParams = formParams; @@ -176,6 +180,11 @@ public List getCookies() { return byteBufferData; } + @Override + public @Nullable ByteBuf getByteBufData() { + return byteBufData; + } + @Override public @Nullable InputStream getStreamData() { return streamData; diff --git a/client/src/main/java/org/asynchttpclient/HostStats.java b/client/src/main/java/org/asynchttpclient/HostStats.java index 9ec52805a..3470ea4e1 100644 --- a/client/src/main/java/org/asynchttpclient/HostStats.java +++ b/client/src/main/java/org/asynchttpclient/HostStats.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java b/client/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java index 0be4dedb5..0df78f7b2 100644 --- a/client/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java +++ b/client/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java @@ -15,6 +15,8 @@ */ package org.asynchttpclient; +import io.netty.buffer.ByteBuf; + import java.nio.ByteBuffer; /** @@ -44,6 +46,12 @@ protected HttpResponseBodyPart(boolean last) { */ public abstract ByteBuffer getBodyByteBuffer(); + /** + * @return the {@link ByteBuf} of the bytes read from the response's chunk. + * The {@link ByteBuf}'s capacity is equal to the number of bytes available. + */ + public abstract ByteBuf getBodyByteBuf(); + /** * @return true if this is the last part. */ diff --git a/client/src/main/java/org/asynchttpclient/Param.java b/client/src/main/java/org/asynchttpclient/Param.java index cbc35e196..4f7a5530a 100644 --- a/client/src/main/java/org/asynchttpclient/Param.java +++ b/client/src/main/java/org/asynchttpclient/Param.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/Request.java b/client/src/main/java/org/asynchttpclient/Request.java index 64977cf71..1d95016b3 100644 --- a/client/src/main/java/org/asynchttpclient/Request.java +++ b/client/src/main/java/org/asynchttpclient/Request.java @@ -16,6 +16,7 @@ */ package org.asynchttpclient; +import io.netty.buffer.ByteBuf; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.resolver.NameResolver; @@ -66,12 +67,14 @@ public interface Request { /** * @return the InetAddress to be used to bypass uri's hostname resolution */ - @Nullable InetAddress getAddress(); + @Nullable + InetAddress getAddress(); /** * @return the local address to bind from */ - @Nullable InetAddress getLocalAddress(); + @Nullable + InetAddress getLocalAddress(); /** * @return the HTTP headers @@ -91,27 +94,38 @@ public interface Request { /** * @return the request's body array of byte arrays (only non-null if it was set this way) */ - @Nullable List getCompositeByteData(); + @Nullable + List getCompositeByteData(); /** * @return the request's body string (only non-null if it was set this way) */ - @Nullable String getStringData(); + @Nullable + String getStringData(); /** * @return the request's body ByteBuffer (only non-null if it was set this way) */ - @Nullable ByteBuffer getByteBufferData(); + @Nullable + ByteBuffer getByteBufferData(); + + /** + * @return the request's body ByteBuf (only non-null if it was set this way) + */ + @Nullable + ByteBuf getByteBufData(); /** * @return the request's body InputStream (only non-null if it was set this way) */ - @Nullable InputStream getStreamData(); + @Nullable + InputStream getStreamData(); /** * @return the request's body BodyGenerator (only non-null if it was set this way) */ - @Nullable BodyGenerator getBodyGenerator(); + @Nullable + BodyGenerator getBodyGenerator(); /** * @return the request's form parameters @@ -126,7 +140,8 @@ public interface Request { /** * @return the virtual host to connect to */ - @Nullable String getVirtualHost(); + @Nullable + String getVirtualHost(); /** * @return the query params resolved from the url/uri @@ -136,22 +151,26 @@ public interface Request { /** * @return the proxy server to be used to perform this request (overrides the one defined in config) */ - @Nullable ProxyServer getProxyServer(); + @Nullable + ProxyServer getProxyServer(); /** * @return the realm to be used to perform this request (overrides the one defined in config) */ - @Nullable Realm getRealm(); + @Nullable + Realm getRealm(); /** * @return the file to be uploaded */ - @Nullable File getFile(); + @Nullable + File getFile(); /** * @return if this request is to follow redirects. Non null values means "override config value". */ - @Nullable Boolean getFollowRedirect(); + @Nullable + Boolean getFollowRedirect(); /** * @return the request timeout. Non zero values means "override config value". @@ -171,7 +190,8 @@ public interface Request { /** * @return the charset value used when decoding the request's body. */ - @Nullable Charset getCharset(); + @Nullable + Charset getCharset(); /** * @return the strategy to compute ChannelPool's keys diff --git a/client/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/client/src/main/java/org/asynchttpclient/RequestBuilderBase.java index dbfd58f5b..dbc5e4144 100644 --- a/client/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/client/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -15,6 +15,7 @@ */ package org.asynchttpclient; +import io.netty.buffer.ByteBuf; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.cookie.Cookie; @@ -76,6 +77,7 @@ public abstract class RequestBuilderBase> { protected @Nullable List compositeByteData; protected @Nullable String stringData; protected @Nullable ByteBuffer byteBufferData; + protected @Nullable ByteBuf byteBufData; protected @Nullable InputStream streamData; protected @Nullable BodyGenerator bodyGenerator; protected @Nullable List formParams; @@ -121,6 +123,7 @@ protected RequestBuilderBase(Request prototype, boolean disableUrlEncoding, bool compositeByteData = prototype.getCompositeByteData(); stringData = prototype.getStringData(); byteBufferData = prototype.getByteBufferData(); + byteBufData = prototype.getByteBufData(); streamData = prototype.getStreamData(); bodyGenerator = prototype.getBodyGenerator(); if (isNonEmpty(prototype.getFormParams())) { @@ -320,6 +323,21 @@ public T addCookie(Cookie cookie) { * @return this */ public T addOrReplaceCookie(Cookie cookie) { + return maybeAddOrReplaceCookie(cookie, true); + } + + /** + * Add a cookie based on its name, if it does not exist yet. Cookies that + * are already set will be ignored. + * + * @param cookie the new cookie + * @return this + */ + public T addCookieIfUnset(Cookie cookie) { + return maybeAddOrReplaceCookie(cookie, false); + } + + private T maybeAddOrReplaceCookie(Cookie cookie, boolean allowReplace) { String cookieKey = cookie.name(); boolean replace = false; int index = 0; @@ -332,10 +350,10 @@ public T addOrReplaceCookie(Cookie cookie) { index++; } - if (replace) { - cookies.set(index, cookie); - } else { + if (!replace) { cookies.add(cookie); + } else if (allowReplace) { + cookies.set(index, cookie); } return asDerivedType(); } @@ -361,6 +379,7 @@ public void resetNonMultipartData() { byteData = null; compositeByteData = null; byteBufferData = null; + byteBufData = null; stringData = null; streamData = null; bodyGenerator = null; @@ -405,6 +424,12 @@ public T setBody(ByteBuffer data) { return asDerivedType(); } + public T setBody(ByteBuf data) { + resetBody(); + byteBufData = data; + return asDerivedType(); + } + public T setBody(InputStream stream) { resetBody(); streamData = stream; @@ -586,6 +611,7 @@ private RequestBuilderBase executeSignatureCalculator() { rb.compositeByteData = compositeByteData; rb.stringData = stringData; rb.byteBufferData = byteBufferData; + rb.byteBufData = byteBufData; rb.streamData = streamData; rb.bodyGenerator = bodyGenerator; rb.virtualHost = virtualHost; @@ -647,6 +673,7 @@ public Request build() { rb.compositeByteData, rb.stringData, rb.byteBufferData, + rb.byteBufData, rb.streamData, rb.bodyGenerator, formParamsCopy, diff --git a/client/src/main/java/org/asynchttpclient/Response.java b/client/src/main/java/org/asynchttpclient/Response.java index 8b9c9a6f1..220d989b0 100644 --- a/client/src/main/java/org/asynchttpclient/Response.java +++ b/client/src/main/java/org/asynchttpclient/Response.java @@ -16,6 +16,7 @@ */ package org.asynchttpclient; +import io.netty.buffer.ByteBuf; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.cookie.Cookie; import org.asynchttpclient.netty.NettyResponse; @@ -61,6 +62,13 @@ public interface Response { */ ByteBuffer getResponseBodyAsByteBuffer(); + /** + * Return the entire response body as a ByteBuf. + * + * @return the entire response body as a ByteBuf. + */ + ByteBuf getResponseBodyAsByteBuf(); + /** * Returns an input stream for the response body. Note that you should not try to get this more than once, and that you should not close the stream. * diff --git a/client/src/main/java/org/asynchttpclient/SslEngineFactory.java b/client/src/main/java/org/asynchttpclient/SslEngineFactory.java index d007106f7..15ec9748e 100644 --- a/client/src/main/java/org/asynchttpclient/SslEngineFactory.java +++ b/client/src/main/java/org/asynchttpclient/SslEngineFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/channel/ChannelPool.java b/client/src/main/java/org/asynchttpclient/channel/ChannelPool.java index 1b38f09a3..4f2bc3b9b 100755 --- a/client/src/main/java/org/asynchttpclient/channel/ChannelPool.java +++ b/client/src/main/java/org/asynchttpclient/channel/ChannelPool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,8 @@ public interface ChannelPool { * @param partitionKey the partition used when invoking offer * @return the channel associated with the uri */ - @Nullable Channel poll(Object partitionKey); + @Nullable + Channel poll(Object partitionKey); /** * Remove all channels from the cache. A channel might have been associated diff --git a/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java b/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java index fd9a51b23..324a4ce34 100644 --- a/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java +++ b/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java b/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java index 106799ddb..e72cc8c13 100644 --- a/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java +++ b/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/channel/NoopChannelPool.java b/client/src/main/java/org/asynchttpclient/channel/NoopChannelPool.java index 3a5b2e93d..ae3aab81a 100644 --- a/client/src/main/java/org/asynchttpclient/channel/NoopChannelPool.java +++ b/client/src/main/java/org/asynchttpclient/channel/NoopChannelPool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,6 @@ public enum NoopChannelPool implements ChannelPool { INSTANCE; /** - * * @return always false since this is a {@link NoopChannelPool} */ @Override @@ -39,7 +38,6 @@ public boolean offer(Channel channel, Object partitionKey) { } /** - * * @return always null since this is a {@link NoopChannelPool} */ @Override @@ -48,7 +46,6 @@ public boolean offer(Channel channel, Object partitionKey) { } /** - * * @return always false since this is a {@link NoopChannelPool} */ @Override @@ -57,7 +54,6 @@ public boolean removeAll(Channel channel) { } /** - * * @return always true since this is a {@link NoopChannelPool} */ @Override @@ -66,7 +62,6 @@ public boolean isOpen() { } /** - * * Does nothing since this is a {@link NoopChannelPool} */ @Override @@ -74,7 +69,6 @@ public void destroy() { } /** - * * Does nothing since this is a {@link NoopChannelPool} */ @Override @@ -82,7 +76,6 @@ public void flushPartitions(Predicate predicate) { } /** - * * @return always {@link Collections#emptyMap()} since this is a {@link NoopChannelPool} */ @Override diff --git a/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java b/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java index 3d4cb6106..3596c67a9 100644 --- a/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java +++ b/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java b/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java index 35a620e31..5832185cc 100644 --- a/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java +++ b/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java @@ -229,7 +229,7 @@ private void removeExpired() { cookieJar.values() .forEach(cookieMap -> removed[0] |= cookieMap.entrySet() - .removeIf(v -> hasCookieExpired(v.getValue().cookie, v.getValue().createdAt))); + .removeIf(v -> hasCookieExpired(v.getValue().cookie, v.getValue().createdAt))); if (removed[0]) { cookieJar.entrySet().removeIf(entry -> entry.getValue() == null || entry.getValue().isEmpty()); diff --git a/client/src/main/java/org/asynchttpclient/handler/MaxRedirectException.java b/client/src/main/java/org/asynchttpclient/handler/MaxRedirectException.java index cf09bc742..993f87d93 100644 --- a/client/src/main/java/org/asynchttpclient/handler/MaxRedirectException.java +++ b/client/src/main/java/org/asynchttpclient/handler/MaxRedirectException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/DiscardEvent.java b/client/src/main/java/org/asynchttpclient/netty/DiscardEvent.java index b8ca2bd55..798312257 100644 --- a/client/src/main/java/org/asynchttpclient/netty/DiscardEvent.java +++ b/client/src/main/java/org/asynchttpclient/netty/DiscardEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/EagerResponseBodyPart.java b/client/src/main/java/org/asynchttpclient/netty/EagerResponseBodyPart.java index 44045e80b..8247379b0 100755 --- a/client/src/main/java/org/asynchttpclient/netty/EagerResponseBodyPart.java +++ b/client/src/main/java/org/asynchttpclient/netty/EagerResponseBodyPart.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; import org.asynchttpclient.HttpResponseBodyPart; import java.nio.ByteBuffer; @@ -53,4 +54,9 @@ public int length() { public ByteBuffer getBodyByteBuffer() { return ByteBuffer.wrap(bytes); } + + @Override + public ByteBuf getBodyByteBuf() { + return Unpooled.wrappedBuffer(bytes); + } } diff --git a/client/src/main/java/org/asynchttpclient/netty/LazyResponseBodyPart.java b/client/src/main/java/org/asynchttpclient/netty/LazyResponseBodyPart.java index 3732669d9..e47277006 100755 --- a/client/src/main/java/org/asynchttpclient/netty/LazyResponseBodyPart.java +++ b/client/src/main/java/org/asynchttpclient/netty/LazyResponseBodyPart.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,8 @@ public LazyResponseBodyPart(ByteBuf buf, boolean last) { this.buf = buf; } - public ByteBuf getBuf() { + @Override + public ByteBuf getBodyByteBuf() { return buf; } diff --git a/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java b/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java index 179d796c1..61fb15161 100755 --- a/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java +++ b/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,9 @@ */ package org.asynchttpclient.netty; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.CompositeByteBuf; import io.netty.handler.codec.http.EmptyHttpHeaders; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.cookie.ClientCookieDecoder; @@ -192,6 +195,15 @@ public ByteBuffer getResponseBodyAsByteBuffer() { return target; } + @Override + public ByteBuf getResponseBodyAsByteBuf() { + CompositeByteBuf compositeByteBuf = ByteBufAllocator.DEFAULT.compositeBuffer(bodyParts.size()); + for (HttpResponseBodyPart part : bodyParts) { + compositeByteBuf.addComponent(true, part.getBodyByteBuf()); + } + return compositeByteBuf; + } + @Override public String getResponseBody() { return getResponseBody(withDefault(extractContentTypeCharsetAttribute(getContentType()), UTF_8)); diff --git a/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java b/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java index f66c9b9d7..c29c0f33d 100755 --- a/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java +++ b/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -187,10 +187,10 @@ public boolean cancel(boolean force) { return false; } - // cancel could happen before channel was attached - if (channel != null) { - Channels.setDiscard(channel); - Channels.silentlyCloseChannel(channel); + final Channel ch = channel; //atomic read, so that it won't end up in TOCTOU + if (ch != null) { + Channels.setDiscard(ch); + Channels.silentlyCloseChannel(ch); } if (ON_THROWABLE_CALLED_FIELD.getAndSet(this, 1) == 0) { diff --git a/client/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java b/client/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java index 49af6b160..567432af3 100755 --- a/client/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java +++ b/client/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/OnLastHttpContentCallback.java b/client/src/main/java/org/asynchttpclient/netty/OnLastHttpContentCallback.java index e72a5c2be..15c0c9617 100644 --- a/client/src/main/java/org/asynchttpclient/netty/OnLastHttpContentCallback.java +++ b/client/src/main/java/org/asynchttpclient/netty/OnLastHttpContentCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java index fe85734c7..c5c94c551 100755 --- a/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java +++ b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/Channels.java b/client/src/main/java/org/asynchttpclient/netty/channel/Channels.java index e0d4acede..c56a05ba5 100755 --- a/client/src/main/java/org/asynchttpclient/netty/channel/Channels.java +++ b/client/src/main/java/org/asynchttpclient/netty/channel/Channels.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java b/client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java index 0ae2f68f7..c4042fdfc 100755 --- a/client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java +++ b/client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java b/client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java index 86d48e451..719733f8a 100755 --- a/client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java +++ b/client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.java b/client/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.java index 919bd3cfa..28a0f359d 100755 --- a/client/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.java +++ b/client/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/AsyncHttpClientHandler.java b/client/src/main/java/org/asynchttpclient/netty/handler/AsyncHttpClientHandler.java index 71d48f408..aeecbef55 100755 --- a/client/src/main/java/org/asynchttpclient/netty/handler/AsyncHttpClientHandler.java +++ b/client/src/main/java/org/asynchttpclient/netty/handler/AsyncHttpClientHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. 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 5990cac42..99a23c7e9 100755 --- a/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java +++ b/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -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/handler/WebSocketHandler.java b/client/src/main/java/org/asynchttpclient/netty/handler/WebSocketHandler.java index 9d26c49b2..1cf19d0ef 100755 --- a/client/src/main/java/org/asynchttpclient/netty/handler/WebSocketHandler.java +++ b/client/src/main/java/org/asynchttpclient/netty/handler/WebSocketHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java index 51e7c8a9b..40628a7e5 100644 --- a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java +++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java @@ -35,7 +35,6 @@ import org.slf4j.LoggerFactory; import java.util.HashSet; -import java.util.List; import java.util.Set; import static io.netty.handler.codec.http.HttpHeaderNames.AUTHORIZATION; @@ -73,11 +72,13 @@ public class Redirect30xInterceptor { private final AsyncHttpClientConfig config; private final NettyRequestSender requestSender; private final MaxRedirectException maxRedirectException; + private final boolean stripAuthorizationOnRedirect; Redirect30xInterceptor(ChannelManager channelManager, AsyncHttpClientConfig config, NettyRequestSender requestSender) { this.channelManager = channelManager; this.config = config; this.requestSender = requestSender; + stripAuthorizationOnRedirect = config.isStripAuthorizationOnRedirect(); // New flag maxRedirectException = unknownStackTrace(new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()), Redirect30xInterceptor.class, "exitAfterHandlingRedirect"); } @@ -127,7 +128,7 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture } } - requestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody)); + requestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody, stripAuthorizationOnRedirect)); // in case of a redirect from HTTP to HTTPS, future // attributes might change @@ -142,11 +143,8 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture CookieStore cookieStore = config.getCookieStore(); if (cookieStore != null) { // Update request's cookies assuming that cookie store is already updated by Interceptors - List cookies = cookieStore.get(newUri); - if (!cookies.isEmpty()) { - for (Cookie cookie : cookies) { - requestBuilder.addOrReplaceCookie(cookie); - } + for (Cookie cookie : cookieStore.get(newUri)) { + requestBuilder.addCookieIfUnset(cookie); } } @@ -183,7 +181,7 @@ public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture return false; } - private static HttpHeaders propagatedHeaders(Request request, Realm realm, boolean keepBody) { + private static HttpHeaders propagatedHeaders(Request request, Realm realm, boolean keepBody, boolean stripAuthorization) { HttpHeaders headers = request.getHeaders() .remove(HOST) .remove(CONTENT_LENGTH); @@ -192,7 +190,7 @@ private static HttpHeaders propagatedHeaders(Request request, Realm realm, boole headers.remove(CONTENT_TYPE); } - if (realm != null && realm.getScheme() == AuthScheme.NTLM) { + if (stripAuthorization || (realm != null && realm.getScheme() == AuthScheme.NTLM)) { headers.remove(AUTHORIZATION) .remove(PROXY_AUTHORIZATION); } diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ResponseFiltersInterceptor.java b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ResponseFiltersInterceptor.java index 19b41f502..5f905d94f 100644 --- a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ResponseFiltersInterceptor.java +++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ResponseFiltersInterceptor.java @@ -20,8 +20,8 @@ import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.filter.FilterContext; import org.asynchttpclient.exception.FilterException; +import org.asynchttpclient.filter.FilterContext; import org.asynchttpclient.filter.ResponseFilter; import org.asynchttpclient.netty.NettyResponseFuture; import org.asynchttpclient.netty.request.NettyRequestSender; diff --git a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java index 785ffad6a..71cc658a0 100755 --- a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java index 07d1f76a2..67d9a67be 100755 --- a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.handler.codec.compression.Brotli; +import io.netty.handler.codec.compression.Zstd; import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.HttpHeaderValues; @@ -32,6 +33,7 @@ import org.asynchttpclient.netty.request.body.NettyBody; import org.asynchttpclient.netty.request.body.NettyBodyBody; import org.asynchttpclient.netty.request.body.NettyByteArrayBody; +import org.asynchttpclient.netty.request.body.NettyByteBufBody; import org.asynchttpclient.netty.request.body.NettyByteBufferBody; import org.asynchttpclient.netty.request.body.NettyCompositeByteArrayBody; import org.asynchttpclient.netty.request.body.NettyDirectBody; @@ -66,6 +68,7 @@ import static org.asynchttpclient.util.HttpUtils.ACCEPT_ALL_HEADER_VALUE; import static org.asynchttpclient.util.HttpUtils.GZIP_DEFLATE; import static org.asynchttpclient.util.HttpUtils.filterOutBrotliFromAcceptEncoding; +import static org.asynchttpclient.util.HttpUtils.filterOutZstdFromAcceptEncoding; import static org.asynchttpclient.util.HttpUtils.hostHeader; import static org.asynchttpclient.util.HttpUtils.originHeader; import static org.asynchttpclient.util.HttpUtils.urlEncodeFormParams; @@ -96,6 +99,8 @@ private NettyBody body(Request request) { nettyBody = new NettyByteBufferBody(StringUtils.charSequence2ByteBuffer(request.getStringData(), bodyCharset)); } else if (request.getByteBufferData() != null) { nettyBody = new NettyByteBufferBody(request.getByteBufferData()); + } else if (request.getByteBufData() != null) { + nettyBody = new NettyByteBufBody(request.getByteBufData()); } else if (request.getStreamData() != null) { nettyBody = new NettyInputStreamBody(request.getStreamData()); } else if (isNonEmpty(request.getFormParams())) { @@ -179,6 +184,11 @@ public NettyRequest newNettyRequest(Request request, boolean performConnectReque // For manual decompression by user, any encoding may suite, so leave untouched headers.set(ACCEPT_ENCODING, filterOutBrotliFromAcceptEncoding(userDefinedAcceptEncoding)); } + if (!Zstd.isAvailable()) { + // zstd is not available. + // For manual decompression by user, any encoding may suit, so leave untouched + headers.set(ACCEPT_ENCODING, filterOutZstdFromAcceptEncoding(userDefinedAcceptEncoding)); + } } } else if (config.isCompressionEnforced()) { // Add Accept Encoding header if compression is enforced @@ -186,6 +196,9 @@ public NettyRequest newNettyRequest(Request request, boolean performConnectReque if (Brotli.isAvailable()) { headers.add(ACCEPT_ENCODING, HttpHeaderValues.BR); } + if (Zstd.isAvailable()) { + headers.add(ACCEPT_ENCODING, HttpHeaderValues.ZSTD); + } } } 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 2c0314325..b66dd713d 100755 --- a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,10 +36,10 @@ import org.asynchttpclient.Realm; import org.asynchttpclient.Realm.AuthScheme; import org.asynchttpclient.Request; +import org.asynchttpclient.exception.FilterException; import org.asynchttpclient.exception.PoolAlreadyClosedException; import org.asynchttpclient.exception.RemotelyClosedException; import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.exception.FilterException; import org.asynchttpclient.filter.IOExceptionFilter; import org.asynchttpclient.handler.TransferCompletionHandler; import org.asynchttpclient.netty.NettyResponseFuture; @@ -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/main/java/org/asynchttpclient/netty/request/WriteProgressListener.java b/client/src/main/java/org/asynchttpclient/netty/request/WriteProgressListener.java index 4a6c330d0..98f669eae 100755 --- a/client/src/main/java/org/asynchttpclient/netty/request/WriteProgressListener.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/WriteProgressListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java b/client/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java index cd0434146..772baca43 100755 --- a/client/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java b/client/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java index 9326d717d..91f2b1ecf 100755 --- a/client/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java index 2dce64353..f38ef3939 100755 --- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java index 610069a01..efe337bfe 100755 --- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java index 5714d1396..b794ab6e9 100755 --- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufBody.java new file mode 100644 index 000000000..d236cdade --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufBody.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015-2023 AsyncHttpClient Project. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.asynchttpclient.netty.request.body; + +import io.netty.buffer.ByteBuf; + +public class NettyByteBufBody extends NettyDirectBody { + + private final ByteBuf bb; + private final CharSequence contentTypeOverride; + private final long length; + + public NettyByteBufBody(ByteBuf bb) { + this(bb, null); + } + + public NettyByteBufBody(ByteBuf bb, CharSequence contentTypeOverride) { + this.bb = bb; + length = bb.readableBytes(); + this.contentTypeOverride = contentTypeOverride; + } + + @Override + public long getContentLength() { + return length; + } + + @Override + public CharSequence getContentTypeOverride() { + return contentTypeOverride; + } + + @Override + public ByteBuf byteBuf() { + return bb; + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.java index 74bd09c4f..e852528fd 100644 --- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.java index 7f8dc69f8..55dfcb8bc 100644 --- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java index e83b7b8cd..a3c40322d 100755 --- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java index 0096cc772..4dba9d951 100755 --- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java index fe889e383..7fa23c07f 100755 --- a/client/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java b/client/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java index 213b539a9..8b0d4373a 100755 --- a/client/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java +++ b/client/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java b/client/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java index 5a72a6b70..74c5d0197 100755 --- a/client/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java +++ b/client/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java b/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java index 67b31c19b..3c9a3675e 100755 --- a/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java +++ b/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java b/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java index 6c2a55ff2..acce84b6d 100755 --- a/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java +++ b/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java b/client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java index 35847dc78..2329edacf 100755 --- a/client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java +++ b/client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java b/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java index ce7624d2b..c2338c46a 100644 --- a/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java +++ b/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java @@ -1083,7 +1083,8 @@ byte[] getChallenge() { /** * Retrieve the target */ - @Nullable String getTarget() { + @Nullable + String getTarget() { return target; } diff --git a/client/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java b/client/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java index 46391c804..048f2e78e 100644 --- a/client/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java +++ b/client/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java @@ -35,5 +35,6 @@ public interface ProxyServerSelector { * @param uri The URI to select a proxy server for. * @return The proxy server to use, if any. May return null. */ - @Nullable ProxyServer select(Uri uri); + @Nullable + ProxyServer select(Uri uri); } diff --git a/client/src/main/java/org/asynchttpclient/request/body/generator/FeedableBodyGenerator.java b/client/src/main/java/org/asynchttpclient/request/body/generator/FeedableBodyGenerator.java index dae8acef8..1aa27f0ac 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/generator/FeedableBodyGenerator.java +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/FeedableBodyGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/request/body/generator/UnboundedQueueFeedableBodyGenerator.java b/client/src/main/java/org/asynchttpclient/request/body/generator/UnboundedQueueFeedableBodyGenerator.java index bc72650a7..f55dcbe37 100755 --- a/client/src/main/java/org/asynchttpclient/request/body/generator/UnboundedQueueFeedableBodyGenerator.java +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/UnboundedQueueFeedableBodyGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/ByteArrayPart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/ByteArrayPart.java index 97ed6cc61..203d37a2c 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/multipart/ByteArrayPart.java +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/ByteArrayPart.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/FileLikePart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/FileLikePart.java index 3370eb761..1d03ad22b 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/multipart/FileLikePart.java +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/FileLikePart.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/FilePart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/FilePart.java index 777489fd4..a156dd077 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/multipart/FilePart.java +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/FilePart.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/Part.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/Part.java index 9dae23f6a..19266eb25 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/multipart/Part.java +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/Part.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/PartBase.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/PartBase.java index 2dff615e4..65e78286f 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/multipart/PartBase.java +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/PartBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/StringPart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/StringPart.java index 2427913ea..05c958d35 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/multipart/StringPart.java +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/StringPart.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/part/PartVisitor.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/PartVisitor.java index 4bddbd7e1..8e9029c4f 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/multipart/part/PartVisitor.java +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/PartVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/uri/Uri.java b/client/src/main/java/org/asynchttpclient/uri/Uri.java index 1c76ff2ce..e1d53d1ca 100644 --- a/client/src/main/java/org/asynchttpclient/uri/Uri.java +++ b/client/src/main/java/org/asynchttpclient/uri/Uri.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/uri/UriParser.java b/client/src/main/java/org/asynchttpclient/uri/UriParser.java index 40c195630..c65f145dd 100644 --- a/client/src/main/java/org/asynchttpclient/uri/UriParser.java +++ b/client/src/main/java/org/asynchttpclient/uri/UriParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/util/HttpUtils.java b/client/src/main/java/org/asynchttpclient/util/HttpUtils.java index f62e2f235..3cca41e61 100644 --- a/client/src/main/java/org/asynchttpclient/util/HttpUtils.java +++ b/client/src/main/java/org/asynchttpclient/util/HttpUtils.java @@ -40,6 +40,7 @@ public final class HttpUtils { private static final String CONTENT_TYPE_CHARSET_ATTRIBUTE = "charset="; private static final String CONTENT_TYPE_BOUNDARY_ATTRIBUTE = "boundary="; private static final String BROTLY_ACCEPT_ENCODING_SUFFIX = ", br"; + private static final String ZSTD_ACCEPT_ENCODING_SUFFIX = ", zstd"; private HttpUtils() { // Prevent outside initialization @@ -173,4 +174,12 @@ public static CharSequence filterOutBrotliFromAcceptEncoding(String acceptEncodi } return acceptEncoding; } + + public static CharSequence filterOutZstdFromAcceptEncoding(String acceptEncoding) { + // we don't support zstd ATM + if (acceptEncoding.endsWith(ZSTD_ACCEPT_ENCODING_SUFFIX)) { + return acceptEncoding.subSequence(0, acceptEncoding.length() - ZSTD_ACCEPT_ENCODING_SUFFIX.length()); + } + return acceptEncoding; + } } diff --git a/client/src/main/java/org/asynchttpclient/util/StringUtils.java b/client/src/main/java/org/asynchttpclient/util/StringUtils.java index bccdd8dd2..0abf0b686 100644 --- a/client/src/main/java/org/asynchttpclient/util/StringUtils.java +++ b/client/src/main/java/org/asynchttpclient/util/StringUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/util/UriEncoder.java b/client/src/main/java/org/asynchttpclient/util/UriEncoder.java index 126edee3d..92706d292 100644 --- a/client/src/main/java/org/asynchttpclient/util/UriEncoder.java +++ b/client/src/main/java/org/asynchttpclient/util/UriEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/java/org/asynchttpclient/ws/WebSocketUtils.java b/client/src/main/java/org/asynchttpclient/ws/WebSocketUtils.java index 8e020ddac..628cc1d7d 100644 --- a/client/src/main/java/org/asynchttpclient/ws/WebSocketUtils.java +++ b/client/src/main/java/org/asynchttpclient/ws/WebSocketUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties b/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties index 6450221b0..f74127c23 100644 --- a/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties +++ b/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties @@ -13,7 +13,7 @@ org.asynchttpclient.maxRedirects=5 org.asynchttpclient.compressionEnforced=false org.asynchttpclient.enableAutomaticDecompression=true org.asynchttpclient.userAgent=AHC/2.1 -org.asynchttpclient.enabledProtocols=TLSv1.2, TLSv1.1, TLSv1 +org.asynchttpclient.enabledProtocols=TLSv1.3, TLSv1.2 org.asynchttpclient.enabledCipherSuites= org.asynchttpclient.filterInsecureCipherSuites=true org.asynchttpclient.useProxySelector=false diff --git a/client/src/test/java/org/asynchttpclient/AbstractBasicTest.java b/client/src/test/java/org/asynchttpclient/AbstractBasicTest.java index 993f87905..2dcfa859d 100644 --- a/client/src/test/java/org/asynchttpclient/AbstractBasicTest.java +++ b/client/src/test/java/org/asynchttpclient/AbstractBasicTest.java @@ -21,9 +21,7 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.AbstractHandler; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; diff --git a/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java b/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java index 954ae2eac..90a515fca 100644 --- a/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java +++ b/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java @@ -441,7 +441,7 @@ public void asyncOptionsTest() throws Throwable { // Some responses contain the TRACE method, some do not - account for both final String[] expected = {"GET", "HEAD", "OPTIONS", "POST"}; final String[] expectedWithTrace = {"GET", "HEAD", "OPTIONS", "POST", "TRACE"}; - Future f = client.prepareOptions("https://www.shieldblaze.com/").execute(new AsyncHandlerAdapter() { + Future f = client.prepareOptions("https://www.google.com/").execute(new AsyncHandlerAdapter() { @Override public State onHeadersReceived(HttpHeaders headers) { diff --git a/client/src/test/java/org/asynchttpclient/AutomaticDecompressionTest.java b/client/src/test/java/org/asynchttpclient/AutomaticDecompressionTest.java new file mode 100644 index 000000000..8f57ffb88 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/AutomaticDecompressionTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2015-2024 AsyncHttpClient Project. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.asynchttpclient; + +import com.aayushatharva.brotli4j.encoder.BrotliOutputStream; +import com.aayushatharva.brotli4j.encoder.Encoder; +import com.github.luben.zstd.Zstd; +import com.sun.net.httpserver.Headers; +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; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +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(50_000); + + private static HttpServer HTTP_SERVER; + + private static AsyncHttpClient createClient() { + AsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder() + .setEnableAutomaticDecompression(true) + .setCompressionEnforced(true) + .build(); + return new DefaultAsyncHttpClient(config); + } + + @BeforeAll + static void setupServer() throws Exception { + HTTP_SERVER = HttpServer.create(new InetSocketAddress(0), 0); + + HTTP_SERVER.createContext("/br").setHandler(new HttpHandler() { + @Override + public void handle(HttpExchange exchange) + throws IOException { + validateAcceptEncodingHeader(exchange); + exchange.getResponseHeaders().set("Content-Encoding", "br"); + exchange.sendResponseHeaders(200, 0); + OutputStream out = exchange.getResponseBody(); + Encoder.Parameters params = new Encoder.Parameters(); + BrotliOutputStream brotliOutputStream = new BrotliOutputStream(out, params); + brotliOutputStream.write(UNCOMPRESSED_PAYLOAD.getBytes(StandardCharsets.UTF_8)); + brotliOutputStream.flush(); + brotliOutputStream.close(); + } + }); + + HTTP_SERVER.createContext("/zstd").setHandler(new HttpHandler() { + @Override + public void handle(HttpExchange exchange) + throws IOException { + validateAcceptEncodingHeader(exchange); + exchange.getResponseHeaders().set("Content-Encoding", "zstd"); + byte[] compressedData = new byte[UNCOMPRESSED_PAYLOAD.length()]; + long n = Zstd.compress(compressedData, UNCOMPRESSED_PAYLOAD.getBytes(StandardCharsets.UTF_8), 2, true); + exchange.sendResponseHeaders(200, n); + OutputStream out = exchange.getResponseBody(); + out.write(compressedData, 0, (int) n); + out.flush(); + out.close(); + } + }); + + HTTP_SERVER.createContext("/gzip").setHandler(new HttpHandler() { + @Override + public void handle(HttpExchange exchange) + throws IOException { + validateAcceptEncodingHeader(exchange); + exchange.getResponseHeaders().set("Content-Encoding", "gzip"); + exchange.sendResponseHeaders(200, 0); + OutputStream out = exchange.getResponseBody(); + GZIPOutputStream gzip = new GZIPOutputStream(out); + gzip.write(UNCOMPRESSED_PAYLOAD.getBytes(StandardCharsets.UTF_8)); + gzip.flush(); + gzip.close(); + } + }); + + HTTP_SERVER.start(); + } + + private static void validateAcceptEncodingHeader(HttpExchange exchange) { + Headers requestHeaders = exchange.getRequestHeaders(); + List acceptEncodingList = requestHeaders.get("Accept-Encoding") + .stream() + .flatMap(x -> Arrays.asList(x.split(",")).stream()) + .collect(Collectors.toList()); + assertEquals(List.of("gzip", "deflate", "br", "zstd"), acceptEncodingList); + } + + @AfterAll + static void stopServer() { + if (HTTP_SERVER != null) { + HTTP_SERVER.stop(0); + } + } + + @Test + void zstd() throws Throwable { + io.netty.handler.codec.compression.Zstd.ensureAvailability(); + try (AsyncHttpClient client = createClient()) { + Request request = new RequestBuilder("GET") + .setUrl("http://localhost:" + HTTP_SERVER.getAddress().getPort() + "/zstd") + .build(); + Response response = client.executeRequest(request).get(); + assertEquals(200, response.getStatusCode()); + assertEquals(UNCOMPRESSED_PAYLOAD, response.getResponseBody()); + } + } + + @Test + void brotli() throws Throwable { + Brotli.ensureAvailability(); + try (AsyncHttpClient client = createClient()) { + Request request = new RequestBuilder("GET") + .setUrl("http://localhost:" + HTTP_SERVER.getAddress().getPort() + "/br") + .build(); + Response response = client.executeRequest(request).get(); + assertEquals(200, response.getStatusCode()); + assertEquals(UNCOMPRESSED_PAYLOAD, response.getResponseBody()); + } + } + + @Test + void gzip() throws Throwable { + try (AsyncHttpClient client = createClient()) { + Request request = new RequestBuilder("GET") + .setUrl("http://localhost:" + HTTP_SERVER.getAddress().getPort() + "/gzip") + .build(); + Response response = client.executeRequest(request).get(); + assertEquals(200, response.getStatusCode()); + assertEquals(UNCOMPRESSED_PAYLOAD, response.getResponseBody()); + } + } + + +} diff --git a/client/src/test/java/org/asynchttpclient/BasicHttpTest.java b/client/src/test/java/org/asynchttpclient/BasicHttpTest.java index cc75c03c1..f83cac80f 100755 --- a/client/src/test/java/org/asynchttpclient/BasicHttpTest.java +++ b/client/src/test/java/org/asynchttpclient/BasicHttpTest.java @@ -79,6 +79,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -559,7 +560,7 @@ public void connectFailureNotifiesHandlerWithConnectException() throws Throwable @Override public void onThrowable(Throwable t) { try { - assertTrue(t instanceof ConnectException); + assertInstanceOf(ConnectException.class, t); } finally { l.countDown(); } @@ -962,8 +963,8 @@ public void requestingPlainHttpEndpointOverHttpsThrowsSslException() throws Thro client.prepareGet(getTargetUrl().replace("http", "https")).execute().get(); fail("Request shouldn't succeed"); } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof ConnectException, "Cause should be a ConnectException"); - assertTrue(e.getCause().getCause() instanceof SSLException, "Root cause should be a SslException"); + assertInstanceOf(ConnectException.class, e.getCause(), "Cause should be a ConnectException"); + assertInstanceOf(SSLException.class, e.getCause().getCause(), "Root cause should be a SslException"); } })); } diff --git a/client/src/test/java/org/asynchttpclient/ClientStatsTest.java b/client/src/test/java/org/asynchttpclient/ClientStatsTest.java index bda1aa3a2..2d8d324de 100644 --- a/client/src/test/java/org/asynchttpclient/ClientStatsTest.java +++ b/client/src/test/java/org/asynchttpclient/ClientStatsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientConfigTest.java b/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientConfigTest.java new file mode 100644 index 000000000..1548d6812 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientConfigTest.java @@ -0,0 +1,30 @@ +package org.asynchttpclient; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DefaultAsyncHttpClientConfigTest { + @Test + void testStripAuthorizationOnRedirect_DefaultIsFalse() { + DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().build(); + assertFalse(config.isStripAuthorizationOnRedirect(), "Default should be false"); + } + + @Test + void testStripAuthorizationOnRedirect_SetTrue() { + DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder() + .setStripAuthorizationOnRedirect(true) + .build(); + assertTrue(config.isStripAuthorizationOnRedirect(), "Should be true when set"); + } + + @Test + void testStripAuthorizationOnRedirect_SetFalse() { + DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder() + .setStripAuthorizationOnRedirect(false) + .build(); + assertFalse(config.isStripAuthorizationOnRedirect(), "Should be false when set to false"); + } +} diff --git a/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientTest.java b/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientTest.java index 0bfd6842c..fc7a1c2db 100644 --- a/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientTest.java +++ b/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientTest.java @@ -50,7 +50,7 @@ public void testNativeTransportWithEpollOnly() throws Exception { AsyncHttpClientConfig config = config().setUseNativeTransport(true).setUseOnlyEpollNativeTransport(true).build(); try (DefaultAsyncHttpClient client = (DefaultAsyncHttpClient) asyncHttpClient(config)) { - assertDoesNotThrow(() -> client.prepareGet("https://www.shieldblaze.com").execute().get()); + assertDoesNotThrow(() -> client.prepareGet("https://www.google.com").execute().get()); assertInstanceOf(EpollEventLoopGroup.class, client.channelManager().getEventLoopGroup()); } } @@ -60,7 +60,7 @@ public void testNativeTransportWithEpollOnly() throws Exception { public void testNativeTransportWithoutEpollOnly() throws Exception { AsyncHttpClientConfig config = config().setUseNativeTransport(true).setUseOnlyEpollNativeTransport(false).build(); try (DefaultAsyncHttpClient client = (DefaultAsyncHttpClient) asyncHttpClient(config)) { - assertDoesNotThrow(() -> client.prepareGet("https://www.shieldblaze.com").execute().get()); + assertDoesNotThrow(() -> client.prepareGet("https://www.google.com").execute().get()); assertInstanceOf(IOUringEventLoopGroup.class, client.channelManager().getEventLoopGroup()); } } @@ -70,7 +70,7 @@ public void testNativeTransportWithoutEpollOnly() throws Exception { public void testNativeTransportKQueueOnMacOs() throws Exception { AsyncHttpClientConfig config = config().setUseNativeTransport(true).build(); try (DefaultAsyncHttpClient client = (DefaultAsyncHttpClient) asyncHttpClient(config)) { - assertDoesNotThrow(() -> client.prepareGet("https://www.shieldblaze.com").execute().get()); + assertDoesNotThrow(() -> client.prepareGet("https://www.google.com").execute().get()); assertInstanceOf(KQueueEventLoopGroup.class, client.channelManager().getEventLoopGroup()); } } diff --git a/client/src/test/java/org/asynchttpclient/PerRequestRelative302Test.java b/client/src/test/java/org/asynchttpclient/PerRequestRelative302Test.java index 4c119779c..ae3eccf85 100644 --- a/client/src/test/java/org/asynchttpclient/PerRequestRelative302Test.java +++ b/client/src/test/java/org/asynchttpclient/PerRequestRelative302Test.java @@ -38,6 +38,7 @@ import static org.asynchttpclient.test.TestUtils.addHttpConnector; import static org.asynchttpclient.test.TestUtils.findFreePort; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -125,7 +126,7 @@ public void redirected302InvalidTest() throws Exception { assertNotNull(e); Throwable cause = e.getCause(); - assertTrue(cause instanceof ConnectException); + assertInstanceOf(ConnectException.class, cause); assertTrue(cause.getMessage().contains(":" + port2)); } diff --git a/client/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java b/client/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java index b04f16533..bee7d0b67 100644 --- a/client/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java +++ b/client/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java @@ -34,6 +34,7 @@ import static org.asynchttpclient.Dsl.config; import static org.asynchttpclient.util.DateUtils.unpreciseMillisTime; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -73,7 +74,7 @@ public void testRequestTimeout() throws IOException { } catch (InterruptedException e) { fail("Interrupted.", e); } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof TimeoutException); + assertInstanceOf(TimeoutException.class, e.getCause()); checkTimeoutMessage(e.getCause().getMessage(), true); } catch (TimeoutException e) { fail("Timeout.", e); @@ -89,7 +90,7 @@ public void testReadTimeout() throws IOException { } catch (InterruptedException e) { fail("Interrupted.", e); } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof TimeoutException); + assertInstanceOf(TimeoutException.class, e.getCause()); checkTimeoutMessage(e.getCause().getMessage(), false); } catch (TimeoutException e) { fail("Timeout.", e); @@ -107,7 +108,7 @@ public void testGlobalDefaultPerRequestInfiniteTimeout() throws IOException { } catch (InterruptedException e) { fail("Interrupted.", e); } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof TimeoutException); + assertInstanceOf(TimeoutException.class, e.getCause()); checkTimeoutMessage(e.getCause().getMessage(), true); } } @@ -121,7 +122,7 @@ public void testGlobalRequestTimeout() throws IOException { } catch (InterruptedException e) { fail("Interrupted.", e); } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof TimeoutException); + assertInstanceOf(TimeoutException.class, e.getCause()); checkTimeoutMessage(e.getCause().getMessage(), true); } catch (TimeoutException e) { fail("Timeout.", e); diff --git a/client/src/test/java/org/asynchttpclient/Relative302Test.java b/client/src/test/java/org/asynchttpclient/Relative302Test.java index b4d254bfb..074930791 100644 --- a/client/src/test/java/org/asynchttpclient/Relative302Test.java +++ b/client/src/test/java/org/asynchttpclient/Relative302Test.java @@ -39,6 +39,7 @@ import static org.asynchttpclient.test.TestUtils.addHttpConnector; import static org.asynchttpclient.test.TestUtils.findFreePort; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -101,7 +102,7 @@ public void redirected302InvalidTest() throws Exception { assertNotNull(e); Throwable cause = e.getCause(); - assertTrue(cause instanceof ConnectException); + assertInstanceOf(ConnectException.class, cause); assertTrue(cause.getMessage().contains(":" + port2)); } diff --git a/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java b/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java index 024fce5f1..34e79121d 100644 --- a/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java +++ b/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java @@ -166,6 +166,40 @@ public void testAddOrReplaceCookies() { assertEquals(requestBuilder.cookies.size(), 2, "cookie size must be 2 after adding 1 more cookie i.e. cookie3"); } + @RepeatedIfExceptionsTest(repeats = 5) + public void testAddIfUnsetCookies() { + RequestBuilder requestBuilder = new RequestBuilder(); + Cookie cookie = new DefaultCookie("name", "value"); + cookie.setDomain("google.com"); + cookie.setPath("/"); + cookie.setMaxAge(1000); + cookie.setSecure(true); + cookie.setHttpOnly(true); + requestBuilder.addCookieIfUnset(cookie); + assertEquals(requestBuilder.cookies.size(), 1, "cookies size should be 1 after adding one cookie"); + assertEquals(requestBuilder.cookies.get(0), cookie, "cookie does not match"); + + Cookie cookie2 = new DefaultCookie("name", "value"); + cookie2.setDomain("google2.com"); + cookie2.setPath("/path"); + cookie2.setMaxAge(1001); + cookie2.setSecure(false); + cookie2.setHttpOnly(false); + + requestBuilder.addCookieIfUnset(cookie2); + assertEquals(requestBuilder.cookies.size(), 1, "cookies size should remain 1 as we just ignored cookie2 because of a cookie with same name"); + assertEquals(requestBuilder.cookies.get(0), cookie, "cookie does not match"); + + Cookie cookie3 = new DefaultCookie("name2", "value"); + cookie3.setDomain("google.com"); + cookie3.setPath("/"); + cookie3.setMaxAge(1000); + cookie3.setSecure(true); + cookie3.setHttpOnly(true); + requestBuilder.addCookieIfUnset(cookie3); + assertEquals(requestBuilder.cookies.size(), 2, "cookie size must be 2 after adding 1 more cookie i.e. cookie3"); + } + @RepeatedIfExceptionsTest(repeats = 5) public void testSettingQueryParamsBeforeUrlShouldNotProduceNPE() { RequestBuilder requestBuilder = new RequestBuilder(); diff --git a/client/src/test/java/org/asynchttpclient/StripAuthorizationOnRedirectHttpTest.java b/client/src/test/java/org/asynchttpclient/StripAuthorizationOnRedirectHttpTest.java new file mode 100644 index 000000000..08c150c08 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/StripAuthorizationOnRedirectHttpTest.java @@ -0,0 +1,95 @@ +package org.asynchttpclient; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class StripAuthorizationOnRedirectHttpTest { + private static HttpServer server; + private static int port; + private static volatile String lastAuthHeader; + + @BeforeAll + public static void startServer() throws Exception { + server = HttpServer.create(new InetSocketAddress(0), 0); + port = server.getAddress().getPort(); + server.createContext("/redirect", new RedirectHandler()); + server.createContext("/final", new FinalHandler()); + server.start(); + } + + @AfterAll + public static void stopServer() { + server.stop(0); + } + + static class RedirectHandler implements HttpHandler { + @Override + public void handle(HttpExchange exchange) { + String auth = exchange.getRequestHeaders().getFirst("Authorization"); + lastAuthHeader = auth; + exchange.getResponseHeaders().add("Location", "http://localhost:" + port + "/final"); + try { + exchange.sendResponseHeaders(302, -1); + } catch (Exception ignored) { + } + exchange.close(); + } + } + + static class FinalHandler implements HttpHandler { + @Override + public void handle(HttpExchange exchange) { + String auth = exchange.getRequestHeaders().getFirst("Authorization"); + lastAuthHeader = auth; + try { + exchange.sendResponseHeaders(200, 0); + exchange.getResponseBody().close(); + } catch (Exception ignored) { + } + exchange.close(); + } + } + + @Test + void testAuthHeaderPropagatedByDefault() throws Exception { + DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder() + .setFollowRedirect(true) + .build(); + try (DefaultAsyncHttpClient client = new DefaultAsyncHttpClient(config)) { + lastAuthHeader = null; + client.prepareGet("http://localhost:" + port + "/redirect") + .setHeader("Authorization", "Bearer testtoken") + .execute() + .get(5, TimeUnit.SECONDS); + // By default, Authorization header is propagated to /final + assertEquals("Bearer testtoken", lastAuthHeader, "Authorization header should be present on redirect by default"); + } + } + + @Test + void testAuthHeaderStrippedWhenEnabled() throws Exception { + DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder() + .setFollowRedirect(true) + .setStripAuthorizationOnRedirect(true) + .build(); + try (DefaultAsyncHttpClient client = new DefaultAsyncHttpClient(config)) { + lastAuthHeader = null; + client.prepareGet("http://localhost:" + port + "/redirect") + .setHeader("Authorization", "Bearer testtoken") + .execute() + .get(5, TimeUnit.SECONDS); + // When enabled, Authorization header should be stripped on /final + assertNull(lastAuthHeader, "Authorization header should be stripped on redirect when enabled"); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java b/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java index 30eded248..5172bae7a 100644 --- a/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java +++ b/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java @@ -13,12 +13,17 @@ package org.asynchttpclient.netty; import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.cookie.Cookie; +import org.asynchttpclient.HttpResponseBodyPart; +import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.TimeZone; @@ -73,4 +78,16 @@ public void testCookieParseWeirdExpiresValue() { Cookie cookie = cookies.get(0); assertEquals(Long.MIN_VALUE, cookie.maxAge()); } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetResponseBodyAsByteBuffer() { + List bodyParts = new LinkedList<>(); + bodyParts.add(new LazyResponseBodyPart(Unpooled.wrappedBuffer("Hello ".getBytes()), false)); + bodyParts.add(new LazyResponseBodyPart(Unpooled.wrappedBuffer("World".getBytes()), true)); + NettyResponse response = new NettyResponse(new NettyResponseStatus(null, null, null), null, bodyParts); + + ByteBuf body = response.getResponseBodyAsByteBuf(); + assertEquals("Hello World", body.toString(StandardCharsets.UTF_8)); + body.release(); + } } diff --git a/client/src/test/java/org/asynchttpclient/netty/NettyTest.java b/client/src/test/java/org/asynchttpclient/netty/NettyTest.java index 9a0293be3..f80c0911e 100644 --- a/client/src/test/java/org/asynchttpclient/netty/NettyTest.java +++ b/client/src/test/java/org/asynchttpclient/netty/NettyTest.java @@ -3,6 +3,7 @@ import io.netty.channel.epoll.Epoll; import io.netty.channel.kqueue.KQueue; import io.netty.handler.codec.compression.Brotli; +import io.netty.handler.codec.compression.Zstd; import io.netty.incubator.channel.uring.IOUring; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledOnOs; @@ -12,32 +13,44 @@ public class NettyTest { @Test - @EnabledOnOs(value = OS.LINUX) + @EnabledOnOs(OS.LINUX) public void epollIsAvailableOnLinux() { assertTrue(Epoll.isAvailable()); } @Test - @EnabledOnOs(value = OS.LINUX) + @EnabledOnOs(OS.LINUX) public void ioUringIsAvailableOnLinux() { assertTrue(IOUring.isAvailable()); } @Test - @EnabledOnOs(value = OS.MAC) + @EnabledOnOs(OS.MAC) public void kqueueIsAvailableOnMac() { assertTrue(KQueue.isAvailable()); } @Test - @EnabledOnOs(value = OS.LINUX) + @EnabledOnOs(OS.LINUX) public void brotliIsAvailableOnLinux() { assertTrue(Brotli.isAvailable()); } @Test - @EnabledOnOs(value = OS.MAC) + @EnabledOnOs(OS.MAC) public void brotliIsAvailableOnMac() { assertTrue(Brotli.isAvailable()); } + + @Test + @EnabledOnOs(OS.LINUX) + public void zstdIsAvailableOnLinux() { + assertTrue(Zstd.isAvailable()); + } + + @Test + @EnabledOnOs(OS.MAC) + public void zstdIsAvailableOnMac() { + assertTrue(Zstd.isAvailable()); + } } diff --git a/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java b/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java index 93506925e..0bce17d4c 100644 --- a/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java +++ b/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -100,7 +100,7 @@ public void testGenerateType1Msg() { @RepeatedIfExceptionsTest(repeats = 5) public void testGenerateType3MsgThrowsExceptionWhenChallengeTooShort() { NtlmEngine engine = new NtlmEngine(); - assertThrows(NtlmEngineException.class, () -> engine.generateType3Msg("username", "password", "localhost", "workstation", + assertThrows(NtlmEngineException.class, () -> NtlmEngine.generateType3Msg("username", "password", "localhost", "workstation", Base64.getEncoder().encodeToString("a".getBytes())), "An NtlmEngineException must have occurred as challenge length is too short"); } @@ -108,7 +108,7 @@ public void testGenerateType3MsgThrowsExceptionWhenChallengeTooShort() { @RepeatedIfExceptionsTest(repeats = 5) public void testGenerateType3MsgThrowsExceptionWhenChallengeDoesNotFollowCorrectFormat() { NtlmEngine engine = new NtlmEngine(); - assertThrows(NtlmEngineException.class, () -> engine.generateType3Msg("username", "password", "localhost", "workstation", + assertThrows(NtlmEngineException.class, () -> NtlmEngine.generateType3Msg("username", "password", "localhost", "workstation", Base64.getEncoder().encodeToString("challenge".getBytes())), "An NtlmEngineException must have occurred as challenge length is too short"); } @@ -125,7 +125,7 @@ public void testGenerateType3MsgThworsExceptionWhenType2IndicatorNotPresent() th buf.write(0); buf.write("challenge".getBytes()); NtlmEngine engine = new NtlmEngine(); - assertThrows(NtlmEngineException.class, () -> engine.generateType3Msg("username", "password", "localhost", "workstation", + assertThrows(NtlmEngineException.class, () -> NtlmEngine.generateType3Msg("username", "password", "localhost", "workstation", Base64.getEncoder().encodeToString(buf.toByteArray())), "An NtlmEngineException must have occurred as type 2 indicator is incorrect"); } } @@ -151,7 +151,7 @@ public void testGenerateType3MsgThrowsExceptionWhenUnicodeSupportNotIndicated() buf.write(longToBytes(1L));// challenge NtlmEngine engine = new NtlmEngine(); - assertThrows(NtlmEngineException.class, () -> engine.generateType3Msg("username", "password", "localhost", "workstation", + assertThrows(NtlmEngineException.class, () -> NtlmEngine.generateType3Msg("username", "password", "localhost", "workstation", Base64.getEncoder().encodeToString(buf.toByteArray())), "An NtlmEngineException must have occurred as unicode support is not indicated"); } @@ -184,7 +184,7 @@ public void testGenerateType3Msg() throws IOException { buf.write(longToBytes(1L));// challenge NtlmEngine engine = new NtlmEngine(); - String type3Msg = engine.generateType3Msg("username", "password", "localhost", "workstation", + String type3Msg = NtlmEngine.generateType3Msg("username", "password", "localhost", "workstation", Base64.getEncoder().encodeToString(buf.toByteArray())); assertEquals(type3Msg, "TlRMTVNTUAADAAAAGAAYAEgAAAAYABgAYAAAABIAEgB4AAAAEAAQAIoAAAAWABYAmgAAAAAAAACwAAAAAQAAAgUBKAoAAAAP1g6lqqN1HZ0wSSxeQ5riQkyh7/UexwVlCPQm0SHU2vsDQm2wM6NbT2zPonPzLJL0TABPAEMAQQBMAEgATwBTAFQAdQBzAGUAcgBuAGEAbQBlAFcATwBSAEsAUwBUAEEAVABJAE8ATgA=", 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/client/src/test/java/org/asynchttpclient/request/body/PutByteBufTest.java b/client/src/test/java/org/asynchttpclient/request/body/PutByteBufTest.java new file mode 100644 index 000000000..3260604d3 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/PutByteBufTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package org.asynchttpclient.request.body; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.Response; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Arrays; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class PutByteBufTest extends AbstractBasicTest { + + private void put(String message) throws Exception { + ByteBuf byteBuf = Unpooled.wrappedBuffer(message.getBytes()); + try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(Duration.ofSeconds(2)))) { + Response response = client.preparePut(getTargetUrl()).setBody(byteBuf).execute().get(); + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getResponseBody(), message); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPutSmallBody() throws Exception { + put("Hello Test"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPutBigBody() throws Exception { + byte[] array = new byte[2048]; + Arrays.fill(array, (byte) 97); + String longString = new String(array, StandardCharsets.UTF_8); + + put(longString); + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new AbstractHandler() { + + @Override + public void handle(String s, Request request, HttpServletRequest httpRequest, HttpServletResponse response) throws IOException { + int size = 1024; + if (request.getContentLength() > 0) { + size = request.getContentLength(); + } + byte[] bytes = new byte[size]; + if (bytes.length > 0) { + final int read = request.getInputStream().read(bytes); + response.getOutputStream().write(bytes, 0, read); + } + + response.setStatus(200); + response.getOutputStream().flush(); + } + }; + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/generator/FeedableBodyGeneratorTest.java b/client/src/test/java/org/asynchttpclient/request/body/generator/FeedableBodyGeneratorTest.java index 4c8d14693..7c2a3579b 100755 --- a/client/src/test/java/org/asynchttpclient/request/body/generator/FeedableBodyGeneratorTest.java +++ b/client/src/test/java/org/asynchttpclient/request/body/generator/FeedableBodyGeneratorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java b/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java index 1ef8201f6..2568b7ae1 100644 --- a/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java +++ b/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/test/java/org/asynchttpclient/testserver/SocksProxy.java b/client/src/test/java/org/asynchttpclient/testserver/SocksProxy.java index d3b193209..b8e67b79f 100644 --- a/client/src/test/java/org/asynchttpclient/testserver/SocksProxy.java +++ b/client/src/test/java/org/asynchttpclient/testserver/SocksProxy.java @@ -39,15 +39,17 @@ public SocksProxy(int runningTime) throws IOException { Set keys = select.selectedKeys(); for (SelectionKey k : keys) { - if (!k.isValid()) + if (!k.isValid()) { continue; + } // new connection? if (k.isAcceptable() && k.channel() == socks) { // server socket SocketChannel csock = socks.accept(); - if (csock == null) + if (csock == null) { continue; + } addClient(csock); csock.register(select, SelectionKey.OP_READ); } else if (k.isReadable()) { @@ -56,14 +58,16 @@ public SocksProxy(int runningTime) throws IOException { SocksClient cl = clients.get(i); try { if (k.channel() == cl.client) // from client (e.g. socks client) + { cl.newClientData(select); - else if (k.channel() == cl.remote) { // from server client is connected to (e.g. website) + } else if (k.channel() == cl.remote) { // from server client is connected to (e.g. website) cl.newRemoteData(); } } catch (IOException e) { // error occurred - remove client cl.client.close(); - if (cl.remote != null) + if (cl.remote != null) { cl.remote.close(); + } k.cancel(); clients.remove(cl); } @@ -75,10 +79,11 @@ else if (k.channel() == cl.remote) { // from server client is connected to (e.g // client timeout check for (int i = 0; i < clients.size(); i++) { SocksClient cl = clients.get(i); - if ((System.currentTimeMillis() - cl.lastData) > 30000L) { + if (System.currentTimeMillis() - cl.lastData > 30000L) { cl.client.close(); - if (cl.remote != null) + if (cl.remote != null) { cl.remote.close(); + } clients.remove(cl); } } @@ -115,8 +120,9 @@ class SocksClient { void newRemoteData() throws IOException { ByteBuffer buf = ByteBuffer.allocate(1024); - if (remote.read(buf) == -1) + if (remote.read(buf) == -1) { throw new IOException("disconnected"); + } lastData = System.currentTimeMillis(); buf.flip(); client.write(buf); @@ -125,8 +131,9 @@ void newRemoteData() throws IOException { void newClientData(Selector selector) throws IOException { if (!connected) { ByteBuffer inbuf = ByteBuffer.allocate(512); - if (client.read(inbuf) < 1) + if (client.read(inbuf) < 1) { return; + } inbuf.flip(); // read socks header @@ -143,13 +150,15 @@ void newClientData(Selector selector) throws IOException { final int port = inbuf.getShort() & 0xffff; - final byte ip[] = new byte[4]; + final byte[] ip = new byte[4]; // fetch IP inbuf.get(ip); InetAddress remoteAddr = InetAddress.getByAddress(ip); - while ((inbuf.get()) != 0) ; // username + while (inbuf.get() != 0) { + ; // username + } // hostname provided, not IP if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] != 0) { // host provided @@ -172,8 +181,9 @@ void newClientData(Selector selector) throws IOException { out.flip(); client.write(out); - if (!remote.isConnected()) + if (!remote.isConnected()) { throw new IOException("connect failed"); + } remote.configureBlocking(false); remote.register(selector, SelectionKey.OP_READ); @@ -181,8 +191,9 @@ void newClientData(Selector selector) throws IOException { connected = true; } else { ByteBuffer buf = ByteBuffer.allocate(1024); - if (client.read(buf) == -1) + if (client.read(buf) == -1) { throw new IOException("disconnected"); + } lastData = System.currentTimeMillis(); buf.flip(); remote.write(buf); diff --git a/client/src/test/java/org/asynchttpclient/uri/UriTest.java b/client/src/test/java/org/asynchttpclient/uri/UriTest.java index 143008e15..f766854e1 100644 --- a/client/src/test/java/org/asynchttpclient/uri/UriTest.java +++ b/client/src/test/java/org/asynchttpclient/uri/UriTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/client/src/test/java/org/asynchttpclient/ws/ProxyTunnellingTest.java b/client/src/test/java/org/asynchttpclient/ws/ProxyTunnellingTest.java index a2177ae96..ce9cda3dc 100644 --- a/client/src/test/java/org/asynchttpclient/ws/ProxyTunnellingTest.java +++ b/client/src/test/java/org/asynchttpclient/ws/ProxyTunnellingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/docs/technical-overview.md b/docs/technical-overview.md deleted file mode 100644 index ad65ec1b9..000000000 --- a/docs/technical-overview.md +++ /dev/null @@ -1,368 +0,0 @@ -# [WIP] AsyncHttpClient Technical Overview - -#### Disclaimer - -This document is a work in progress. - -## Motivation - -While heavily used (~2.3M downloads across the project in December 2020 alone), AsyncHttpClient (or AHC) does not - at this point in time - have a single guiding document that -explains how it works internally. As a maintainer fresh on the scene it was unclear to me ([@TomGranot](https://github.com/TomGranot)) exactly how all the pieces fit together. - -As part of the attempt to educate myself, I figured it would be a good idea to write a technical overview of the project. This document provides an in-depth walkthtough of the -library, allowing new potential contributors to "hop on" the coding train as fast as possible. - -Note that this library *is not small*. I expect that in addition to offering a better understanding as to how each piece *works*, writing this document will also allow me to -understand which pieces *do not work* as well as expected, and direct me towards things that need a little bit of love. - -PRs are open for anyone who wants to help out. For now - let the fun begin. :) - -**Note: I wrote this guide while using AHC 2.12.2**. - -## The flow of a request - -### Introduction - -AHC is an *Asynchronous* HTTP Client. That means that it needs to have some underlying mechanism of dealing with response data that arrives **asynchronously**. To make that -part easier, the creator of the library ([@jfarcand](https://github.com/jfarcand)) built it on top of [Netty](https://netty.io/), which is ( -by [their own definition](https://netty.io/#content:~:text=Netty%20is%20a%20NIO%20client%20server,as%20TCP%20and%20UDP%20socket%20server.)) "a framework that enables quick and -easy development of network applications". - -This article is not a Netty user guide. If you're interested in all Netty has to offer, you should check out -the [official user guide](https://netty.io/wiki/user-guide-for-4.x.html). This article is, instead, more of a discussion of using Netty *in the wild* - an overview of what a -client library built on top of Netty actually looks like in practice. - -### The code in full - -The best way to explore what the client actually does is, of course, by following the path a request takes. - -Consider the following bit of -code, [taken verbatim from one of the simplest tests](https://github.com/AsyncHttpClient/async-http-client/blob/2b12d0ba819e05153fa265b4da7ca900651fd5b3/client/src/test/java/org/asynchttpclient/BasicHttpTest.java#L81-L91) -in the library: - -```java -@Test - public void getRootUrl() throws Throwable { - withClient().run(client -> - withServer(server).run(server -> { - String url = server.getHttpUrl(); - server.enqueueOk(); - - Response response = client.executeRequest(get(url), new AsyncCompletionHandlerAdapter()).get(TIMEOUT, SECONDS); - assertEquals(response.getUri().toUrl(), url); - })); - } -``` - -Let's take it bit by bit. - -First: - -```java -withClient().run(client -> - withServer(server).run(server -> { - String url = server.getHttpUrl(); - server.enqueueOk(); -``` - -These lines take care of spinning up a server to run the test against, and create an instance of `AsyncHttpClient` called `client`. If you were to drill deeper into the code, -you'd notice that the instantiation of `client` can be simplified to (converted from functional to procedural for the sake of the explanation): - -```java -DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().build.()setMaxRedirects(0); -AsyncHttpClient client = new DefaultAsyncHttpClient(config); -``` - -Once the server and the client have been created, we can now run our test: - -```java -Response response = client.executeRequest(get(url), new AsyncCompletionHandlerAdapter()).get(TIMEOUT, SECONDS); -assertEquals(response.getUri().toUrl(), url); -``` - -The first line executes a `GET` request to the URL of the server that was previously spun up, while the second line is the assertion part of our test. Once the request is -completed, the final `Response` object is returned. - -The intersting bits, of course, happen between the lines - and the best way to start the discussion is by considering what happens under the hood when a new client is -instantiated. - -### Creating a new AsyncHTTPClient - Configuration - -AHC was designed to be *heavily configurable*. There are many, many different knobs you can turn and buttons you can press in order to get it to behave _just right_ -. [`DefaultAsyncHttpClientConfig`](https://github.com/AsyncHttpClient/async-http-client/blob/d4f1e5835b81a5e813033ba2a64a07b020c70007/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java) -is a utility class that pulls a set -of [hard-coded, sane defaults](https://github.com/AsyncHttpClient/async-http-client/blob/d4f1e5835b81a5e813033ba2a64a07b020c70007/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties) -to prevent you from having to deal with these mundane configurations - please check it out if you have the time. - -A keen observer will note that the construction of a `DefaultAsyncHttpClient` is done using -the [Builder Pattern](https://dzone.com/articles/design-patterns-the-builder-pattern) - this is useful, since many times you want to change a few parameters in a -request (`followRedirect`, `requestTimeout`, etc... ) but still rely on the rest of the default configuration properties. The reason I'm mentioning this here is to prevent a -bit of busywork on your end the next time you want to create a client - it's much, much easier to work off of the default client and tweak properties than creating your own -set of configuration properties. - -The `setMaxRedicrects(0)` from the initialization code above is an example of doign this in practice. Having no redirects following the `GET` requeset is useful in the context -of the test, and so we turn a knob to ensure none do. - -### Creating a new AsyncHTTPClient - Client Instantiation - -Coming back to our example - once we've decided on a proper configuration, it's time to create a client. Let's look at the constructor of -the [`DefaultAsyncHttpClient`](https://github.com/AsyncHttpClient/async-http-client/blob/a44aac86616f4e8ffe6977dfef0f0aa460e79d07/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java): - -```java - public DefaultAsyncHttpClient(AsyncHttpClientConfig config) { - - this.config = config; - this.noRequestFilters = config.getRequestFilters().isEmpty(); - allowStopNettyTimer = config.getNettyTimer() == null; - nettyTimer = allowStopNettyTimer ? newNettyTimer(config) : config.getNettyTimer(); - - channelManager = new ChannelManager(config, nettyTimer); - requestSender = new NettyRequestSender(config, channelManager, nettyTimer, new AsyncHttpClientState(closed)); - channelManager.configureBootstraps(requestSender); - - CookieStore cookieStore = config.getCookieStore(); - if (cookieStore != null) { - int cookieStoreCount = config.getCookieStore().incrementAndGet(); - if ( - allowStopNettyTimer // timer is not shared - || cookieStoreCount == 1 // this is the first AHC instance for the shared (user-provided) timer - ) { - nettyTimer.newTimeout(new CookieEvictionTask(config.expiredCookieEvictionDelay(), cookieStore), - config.expiredCookieEvictionDelay(), TimeUnit.MILLISECONDS); - } - } - } -``` - -The constructor actually reveals a lot of the moving parts of AHC, and is worth a proper walkthrough: - -#### `RequestFilters` - -```java - this.noRequestFilters = config.getRequestFilters().isEmpty(); -``` - -`RequestFilters` are a way to perform some form of computation **before sending a request to a server**. You can read more about request filters [here](#request-filters), but -a simple example is -the [ThrottleRequestFilter](https://github.com/AsyncHttpClient/async-http-client/blob/758dcf214bf0ec08142ba234a3967d98a3dc60ef/client/src/main/java/org/asynchttpclient/filter/ThrottleRequestFilter.java) -that throttles requests by waiting for a response to arrive before executing the next request in line. - -Note that there is another set of filters, `ResponseFilters`, that can perform computations **before processing the first byte of the response**. You can read more about -them [here](#response-filters). - -#### `NettyTimer` - -```java -allowStopNettyTimer = config.getNettyTimer() == null; -nettyTimer = allowStopNettyTimer ? newNettyTimer(config) : config.getNettyTimer(); -``` - -`NettyTimer` is actually not a timer, but a *task executor* that waits an arbitrary amount of time before performing the next task. In the case of the code above, it is used -for evicting cookies after they expire - but it has many different use cases (request timeouts being a prime example). - -#### `ChannelManager` - -```java -channelManager = new ChannelManager(config, nettyTimer); -``` - -`ChannelManager` requires a [section of its own](#channelmanager), but the bottom line is that one has to do a lot of boilerplate work with Channels when building an HTTP -client using Netty. For any given request there's a variable number of channel operations you would have to take, and there's a lot of value in re-using existing channels in -clever ways instead of opening new ones. `ChannelManager` is AHC's way of encapsulating at least some of that functionality (for -example, [connection pooling](https://en.wikipedia.org/wiki/Connection_pool#:~:text=In%20software%20engineering%2C%20a%20connection,executing%20commands%20on%20a%20database.)) -into a single object, instead of having it spread out all over the place. - -There are two similiarly-named constructs in the project, so I'm mentioning them in this - -* `ChannelPool`, as it - is [implemented in AHC](https://github.com/AsyncHttpClient/async-http-client/blob/758dcf214bf0ec08142ba234a3967d98a3dc60ef/client/src/main/java/org/asynchttpclient/channel/ChannelPool.java#L21) - , is an **AHC structure** designed to be a "container" of channels - a place you can add and remove channels from as the need arises. Note that the AHC implementation (that - might go as far back as 2012) *predates* the [Netty implementation](https://netty.io/news/2015/05/07/4-0-28-Final.html) introduced in 2015 (see - this [AHC user guide entry](https://asynchttpclient.github.io/async-http-client/configuring.html#contentBox:~:text=ConnectionsPoo,-%3C) from 2012 in which `ConnectionPool` - is referenced as proof). - - As - the [Netty release mentions](https://netty.io/news/2015/05/07/4-0-28-Final.html#main-content:~:text=Many%20of%20our%20users%20needed%20to,used%20Netty%20to%20writing%20a%20client.) - , connection pooling in the world of Netty-based clients is a valuable feature to have, one that [Jean-Francois](https://github.com/jfarcand) implemented himself instead of - waiting for Netty to do so. This might confuse anyone coming to the code a at a later point in time - like me - and I have yet to explore the tradeoffs of stripping away the - current implementation and in favor of the upstream one. See [this issue](https://github.com/AsyncHttpClient/async-http-client/issues/1766) for current progress. - -* [`ChannelGroup`](https://netty.io/4.0/api/io/netty/channel/group/ChannelGroup.html) (not to be confused with `ChannelPool`) is a **Netty structure** designed to work with - Netty `Channel`s *in bulk*, to reduce the need to perform the same operation on multiple channels sequnetially. - -#### `NettyRequestSender` - -```java -requestSender = new NettyRequestSender(config, channelManager, nettyTimer, new AsyncHttpClientState(closed)); -channelManager.configureBootstraps(requestSender); -``` - -`NettyRequestSender` does the all the heavy lifting required for sending the HTTP request - creating the required `Request` and `Response` objects, making sure `CONNECT` -requests are sent before the relevant requests, dealing with proxy servers (in the case -of [HTTPS connections](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT)), dispatching DNS hostname resolution requests and more. - -A few extra comments before we move on: - -* When finished with all the work, `NettyRequestSender` will send back - a [`ListenableFuture`](https://github.com/AsyncHttpClient/async-http-client/blob/d47c56e7ee80b76a4cffd4770237239cfea0ffd6/client/src/main/java/org/asynchttpclient/ListenableFuture.java#L40) - . AHC's `ListenableFuture` is an extension of a normal Java `Future` that allows for the addition of "Listeners" - pieces of code that get executed once the computation (the - one blocking the `Future` from completing) is finished. It is an example of a *very* common abstraction that exists in many different Java projects - - Google's [Guava](https://github.com/google/guava) [has one](https://github.com/google/guava/blob/master/futures/listenablefuture1/src/com/google/common/util/concurrent/ListenableFuture.java) - , for example, and so does [Spring](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/concurrent/ListenableFuture.html)). - -* Note the invocation of `configureBootstraps` in the second line here. `Bootstrap`s are a Netty concept that make it easy to set up `Channel`s - we'll talk about them a bit - later. - -#### `CookieStore` - -```java -CookieStore cookieStore = config.getCookieStore(); - if (cookieStore != null) { - int cookieStoreCount = config.getCookieStore().incrementAndGet(); - if ( - allowStopNettyTimer // timer is not shared - || cookieStoreCount == 1 // this is the first AHC instance for the shared (user-provided) timer - ) { - nettyTimer.newTimeout(new CookieEvictionTask(config.expiredCookieEvictionDelay(), cookieStore), - config.expiredCookieEvictionDelay(), TimeUnit.MILLISECONDS); - } - } -``` - -`CookieStore` is, well, a container for cookies. In this context, it is used to handle the task of cookie eviction (removing cookies whose expiry date has passed). This is, by -the way, an example of one of the *many, many features* AHC supports out of the box that might not be evident upon first observation. - -Once the client has been properly configured, it's time to actually execute the request. - -### Executing a request - Before execution - -Take a look at the execution line from the code above again: - -```java -Response response = client.executeRequest(get(url), new AsyncCompletionHandlerAdapter()).get(TIMEOUT, SECONDS); -``` - -Remember that what we have in front of us is an instance of `AsyncHttpClient` called `client` that is configured with an `AsyncHttpClientConfig`, and more specifically an -instance of `DefaultAsyncHttpClient` that is configured with `DefaultAsyncHttpClientConfig`. - -The `executeRequest` method is passed two arguments, and returns a `ListenableFuture`. The `Response` created by executing the `get` method on the `ListenableFuture` is the -end of the line in our case here, since this test is very simple - there's no response body to parse or any other computations to do in order to assert the test succeeded. The -only thing that is required for the correct operation of the code is for the `Response` to come back with the correct URL. - -Let's turn our eyes to the two arguments passed to `executeRequest`, then, since they are the key parts here: - -1. `get(url)` is the functional equivalent of `new RequestBuilder("GET").setUrl(url)`. `RequestBuilder` is in charge of scaffolding an instance - of [AHC's `Request` object](https://github.com/AsyncHttpClient/async-http-client/blob/c5eff423ebdd0cddd00bc6fcf17682651a151028/client/src/main/java/org/asynchttpclient/Request.java) - and providing it with sane defaults - mostly regarding HTTP headers (`RequestBuilder` does for `Request` what `DefaultAsyncHttpClientConfig.Builder()` does - for `DefaultAsyncHttpClient`). - 1. In our case, the `Request` contains no body (it's a simple `GET`). However, if that request was, for example, a `POST` - it could have a payload that would need to be - sent to the server via HTTP as well. We'll be talking about `Request` in more detail [here](#working-with-request-bodies), including how to work with request bodies. -2. To fully understand what `AsyncCompletionHandlerAdapter` is, and why it's such a core piece of everything that goes on in AHC, a bit of Netty background is required. Let's - take a sidestep for a moment: - -#### Netty `Channel`s and their associated entities ( `ChannelPipeline`s, `ChannelHandler`s and `ChannelAdapter`s) - -Recall that AHC is built on [Netty](https://netty.io/) and its networking abstractions. If you want to dive deeper into the framework you **should** -read [Netty in Action](https://www.manning.com/books/netty-in-action) (great book, Norman!), but for the sake of our discussion it's enough to settle on clarifying a few basic -terms: - -1. [`Channel`](https://netty.io/4.1/api/io/netty/channel/Channel.html) is Netty's version of a normal - Java [`Socket`](https://docs.oracle.com/javase/8/docs/api/java/net/Socket.html), greatly simplified for easier usage. It - encapsulates [all that you can know and do](https://netty.io/4.1/api/io/netty/channel/Channel.html#allclasses_navbar_top:~:text=the%20current%20state%20of%20the%20channel,and%20requests%20associated%20with%20the%20channel.) - with a regular `Socket`: - - 1. **State** - Is the socket currently open? Is it currently closed? - 2. **I/O Options** - Can we read from it? Can we write to it? - 3. **Configuration** - What is the receive buffer size? What is the connect timeout? - 4. `ChannelPipeline` - A reference to this `Channel`'s `ChannelPipeline`. - -2. Note that operations on a channel, in and of themselves, are **blocking** - that is, any operation that is performed on a channel blocks any other operations from being - performed on the channel at any given point in time. This is contrary to the Asynchronous nature Netty purports to support. - - To solve the issue, Netty adds a `ChannelPipeline` to every new `Channel` that is initialised. A `ChannelPipeline` is nothing but a container for `ChannelHandlers`. -3. [`ChannelHandler`](https://netty.io/4.1/api/io/netty/channel/ChannelHandler.html)s encapsulate the application logic of a Netty application. To be more precise, a *chain* - of `ChannelHandler`s, each in charge of one or more small pieces of logic that - when taken together - describe the entire data processing that is supposed to take place - during the lifetime of the application. - -4. [`ChannelHandlerContext`](https://netty.io/4.0/api/io/netty/channel/ChannelHandlerContext.html) is also worth mentioning here - it's the actual mechanism a `ChannelHandler` - uses to talk to the `ChannelPipeline` that encapsulates it. - -5. `ChannelXHandlerAdapter`s are a set of *default* handler implementations - "sugar" that should make the development of application logic easier. `X` can - be `Inbound ` (`ChannelInboundHandlerAdapter`), `Oubound` (`ChannelOutboundHandlerAdapter`) or one of many other options Netty provides out of the box. - -#### `ChannelXHandlerAdapter` VS. `AsyncXHandlerAdapter` - -This where it's important to note the difference between `ChannelXHandlerAdapter` (i.e. `ChannelInboundHandlerAdapater`) - which is a **Netty construct** -and `AsyncXHandlerAdapter` (i.e. `AsyncCompletionHandlerAdapater`) - which is an **AHC construct**. - -Basically, `ChannelXHandlerAdapter` is a Netty construct that provides a default implementation of a `ChannelHandler`, while `AsyncXHandlerAdapter` is an AHC construct that -provides a default implementation of an `AsyncHandler`. - -A `ChannelXHandlerAdapter` has methods that are called when *handler-related* and *channel-related* events occur. When the events "fire", a piece of business logic is carried -out in the relevant method, and the operation is then **passed on to the** **next `ChannelHandler` in line.** *The methods return nothing.* - -An `AsyncXHandlerAdapter` works a bit differently. It has methods that are triggered when *some piece of data is available during an asynchronous response processing*. The -methods are invoked in a pre-determined order, based on the expected arrival of each piece of data (when the status code arrives, when the headers arrive, etc.). When these -pieces of information become availale, a piece of business logic is carried out in the relevant method, and * -a [`STATE`](https://github.com/AsyncHttpClient/async-http-client/blob/f61f88e694850818950195379c5ba7efd1cd82ee/client/src/main/java/org/asynchttpclient/AsyncHandler.java#L242-L253) -is returned*. This `STATE` enum instructs the current implementation of the `AsyncHandler` (in our case, `AsyncXHandlerAdapater`) whether it should `CONTINUE` or `ABORT` the -current processing. - -This is **the core of AHC**: an asynchronous mechanism that encodes - and allows a developer to "hook" into - the various stages of the asynchronous processing of an HTTP -response. - -### Executing a request - During execution - -TODO - -### Executing a request - After execution - -TODO - -## Working with Netty channels - -### ChannelManager - -TODO - -## Transforming requests and responses - -TODO - -### Working with Request Bodies - -TODO - -### Request Filters - -TODO - -### Working with Response Bodies - -TODO - -### Response Filters - -TODO - -### Handlers - -TODO - -## Resources - -### Netty - -* https://seeallhearall.blogspot.com/2012/05/netty-tutorial-part-1-introduction-to.html - -### AsyncHttpClient - -TODO - -### HTTP - -TODO - -## Footnotes - -[^1] Some Netty-related definitions borrow heavily from [here](https://seeallhearall.blogspot.com/2012/05/netty-tutorial-part-1-introduction-to.html). diff --git a/pom.xml b/pom.xml index 3965c62ef..ee1c2308c 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ org.asynchttpclient async-http-client-project - 3.0.0.Beta3 + 3.0.2 pom AHC/Project @@ -39,34 +39,30 @@ - - - slandelle - Stephane Landelle - slandelle@gatling.io - - - hyperxpro - Aayush Atharva - aayush@shieldblaze.com - - - 11 11 11 UTF-8 - 4.1.107.Final - 0.0.25.Final - 1.16.0 - 2.0.12 + 4.1.119.Final + 0.0.26.Final + 1.18.0 + 2.0.16 + 1.5.7-2 2.0.1 - 1.4.11 - 24.0.1 + 1.5.18 + 26.0.2 + + + hyperxpro + Aayush Atharva + aayush@shieldblaze.com + + + scm:git:git@github.com:AsyncHttpClient/async-http-client.git scm:git:git@github.com:AsyncHttpClient/async-http-client.git @@ -109,14 +105,14 @@ org.junit junit-bom - 5.10.2 + 5.11.4 pom import io.github.nettyplus netty-leak-detector-junit-extension - 0.0.2 + 0.0.8 @@ -224,6 +220,13 @@ true + + com.github.luben + zstd-jni + ${zstd-jni.version} + true + + com.aayushatharva.brotli4j brotli4j @@ -290,7 +293,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.12.1 + 3.14.0 11 11 @@ -319,12 +322,12 @@ com.google.errorprone error_prone_core - 2.25.0 + 2.31.0 com.uber.nullaway nullaway - 0.10.10 + 0.12.6 @@ -334,7 +337,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.5 + 3.5.2 @{argLine} --add-exports java.base/jdk.internal.misc=ALL-UNNAMED @@ -345,7 +348,7 @@ org.jacoco jacoco-maven-plugin - 0.8.9 + 0.8.12 @@ -368,7 +371,6 @@ 3.2.1 - attach-sources jar-no-fork @@ -379,7 +381,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.6.3 + 3.11.1 attach-javadocs @@ -393,7 +395,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.13 + 1.7.0 true ossrh @@ -406,7 +408,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.1.0 + 3.2.7 sign-artifacts @@ -420,10 +422,38 @@ --pinentry-mode loopback + false + + + com.github.siom79.japicmp + japicmp-maven-plugin + 0.23.1 + + + RELEASE + ${project.version} + + + true + true + true + false + public + + + + + + cmp + + verify + + +