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..6e56bf9fc 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,20 +30,28 @@ org.asynchttpclient.client - 11.0.16 - 10.1.19 - 2.11.0 + 11.0.24 + 10.1.42 + 2.18.0 4.11.0 - 2.2 - 2.0.2 + 3.0 + 2.1.0 + + + hyperxpro + Aayush Atharva + aayush@shieldblaze.com + + + commons-fileupload commons-fileupload - 1.5 + 1.6.0 test 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..c91ed6bda 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. @@ -15,13 +15,13 @@ */ package org.asynchttpclient.channel; +import java.util.Objects; + import org.asynchttpclient.proxy.ProxyServer; import org.asynchttpclient.proxy.ProxyType; import org.asynchttpclient.uri.Uri; import org.jetbrains.annotations.Nullable; -import java.util.Objects; - @FunctionalInterface public interface ChannelPoolPartitioning { @@ -111,7 +111,8 @@ public String toString() { ", virtualHost=" + virtualHost + ", proxyHost=" + proxyHost + ", proxyPort=" + proxyPort + - ", proxyType=" + proxyType; + ", proxyType=" + proxyType + + ")"; } } } 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..9d64fc54b 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.13.0 pom import io.github.nettyplus netty-leak-detector-junit-extension - 0.0.2 + 0.2.0 @@ -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 + + + pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy