diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index b55e0465d..2586cf3c6 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -5,39 +5,55 @@ 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@v3 + - uses: actions/checkout@v4 - name: Grant Permission - run: sudo chmod +x ./mvnw - - uses: actions/setup-java@v3 + 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@v3 + - uses: actions/checkout@v4 - name: Grant Permission - run: sudo chmod +x ./mvnw - - uses: actions/setup-java@v3 + 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@v3 - - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: 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/maven.yml b/.github/workflows/maven.yml index 9b57efa7f..1f49d3112 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -20,10 +20,10 @@ jobs: RunOnLinux: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Grant Permission run: sudo chmod +x ./mvnw - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: 'corretto' java-version: '11' @@ -33,10 +33,10 @@ jobs: RunOnMacOs: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Grant Permission run: sudo chmod +x ./mvnw - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: 'corretto' java-version: '11' @@ -46,8 +46,8 @@ jobs: RunOnWindows: runs-on: windows-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: distribution: 'corretto' java-version: '11' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3851c42e5..b175fa865 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,12 +13,12 @@ jobs: Publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Grant Permission run: sudo chmod +x ./mvnw - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: 'corretto' java-version: '11' @@ -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/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar index bf82ff01c..c1dd12f17 100644 Binary files a/.mvn/wrapper/maven-wrapper.jar and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index df0cd8a0f..4a95a1367 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -5,14 +5,14 @@ # to you 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 -# -# https://www.apache.org/licenses/LICENSE-2.0 -# +# +# 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. -distributionUrl=https://maven-central.storage-download.googleapis.com/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip -wrapperUrl=https://maven-central.storage-download.googleapis.com/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 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 44555f076..0272134ed 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Maven: org.asynchttpclient async-http-client - 3.0.0.Beta2 + 3.0.2 ``` @@ -28,24 +28,10 @@ Maven: Gradle: ```groovy dependencies { - implementation 'org.asynchttpclient:async-http-client:3.0.0.Beta2' + 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 eaab71582..733f20b51 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -19,7 +19,7 @@ org.asynchttpclient async-http-client-project - 3.0.0.Beta2 + 3.0.2 4.0.0 @@ -30,14 +30,22 @@ org.asynchttpclient.client - 11.0.15 - 10.1.8 - 2.11.0 + 11.0.24 + 10.1.40 + 2.18.0 4.11.0 - 2.2 - 2.0.2 + 3.0 + 2.1.0 + + + hyperxpro + Aayush Atharva + aayush@shieldblaze.com + + + @@ -79,7 +87,6 @@ org.junit.jupiter junit-jupiter-api - 5.9.0 test @@ -87,7 +94,6 @@ org.junit.jupiter junit-jupiter-engine - 5.9.0 test @@ -95,7 +101,12 @@ org.junit.jupiter junit-jupiter-params - 5.9.0 + test + + + + io.github.nettyplus + netty-leak-detector-junit-extension test diff --git a/client/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java b/client/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java index e5cb67207..63335cb29 100644 --- a/client/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java +++ b/client/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java @@ -98,7 +98,7 @@ public State onHeadersWritten() { } /** - * Invoked when the content (a {@link File}, {@link String} or + * Invoked when the content (a {@link File}, {@link String}) or * {@link InputStream} has been fully written on the I/O socket. * * @return a {@link AsyncHandler.State} telling to CONTINUE diff --git a/client/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java b/client/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java index 8ad58eff6..25fc9da18 100644 --- a/client/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java +++ b/client/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java @@ -23,6 +23,7 @@ * Simple {@link AsyncHandler} of type {@link Response} */ public class AsyncCompletionHandlerBase extends AsyncCompletionHandler { + @Override public @Nullable Response onCompleted(@Nullable Response response) throws Exception { return response; diff --git a/client/src/main/java/org/asynchttpclient/AsyncHandler.java b/client/src/main/java/org/asynchttpclient/AsyncHandler.java index 74099cc4d..22451fe09 100644 --- a/client/src/main/java/org/asynchttpclient/AsyncHandler.java +++ b/client/src/main/java/org/asynchttpclient/AsyncHandler.java @@ -54,7 +54,7 @@ *

* Do NOT perform any blocking operations in any of these methods. A typical example would be trying to send another * request and calling get() on its future. - * There's a chance you might end up in a dead lock. + * There's a chance you might end up in a deadlock. * If you really need to perform a blocking operation, execute it in a different dedicated thread pool. * * @param Type of object returned by the {@link Future#get} @@ -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 @@ -151,7 +152,7 @@ default void onHostnameResolutionFailure(String name, Throwable cause) { /** * Notify the callback when trying to open a new connection. *

- * Might be called several times if the name was resolved to multiple addresses and we failed to connect to the first(s) one(s). + * Might be called several times if the name was resolved to multiple addresses, and we failed to connect to the first(s) one(s). * * @param remoteAddress the address we try to connect to */ diff --git a/client/src/main/java/org/asynchttpclient/AsyncHttpClient.java b/client/src/main/java/org/asynchttpclient/AsyncHttpClient.java index 9e86b9f84..01a3ecf73 100755 --- a/client/src/main/java/org/asynchttpclient/AsyncHttpClient.java +++ b/client/src/main/java/org/asynchttpclient/AsyncHttpClient.java @@ -66,7 +66,7 @@ * The {@link AsyncCompletionHandler#onCompleted(Response)} method will be invoked once the http response has been fully read. * The {@link Response} object includes the http headers and the response body. Note that the entire response will be buffered in memory. *
- * You can also have more control about the how the response is asynchronously processed by using an {@link AsyncHandler} + * You can also have more control about how the response is asynchronously processed by using an {@link AsyncHandler} *

  *      AsyncHttpClient c = new AsyncHttpClient();
  *      Future<String> f = c.prepareGet(TARGET_URL).execute(new AsyncHandler<String>() {
@@ -149,7 +149,7 @@ public interface AsyncHttpClient extends Closeable {
      * Prepare an HTTP client request.
      *
      * @param method HTTP request method type. MUST BE in upper case
-     * @param url    A well formed URL.
+     * @param url    A well-formed URL.
      * @return {@link RequestBuilder}
      */
     BoundRequestBuilder prepare(String method, String url);
@@ -158,7 +158,7 @@ public interface AsyncHttpClient extends Closeable {
     /**
      * Prepare an HTTP client GET request.
      *
-     * @param url A well formed URL.
+     * @param url A well-formed URL.
      * @return {@link RequestBuilder}
      */
     BoundRequestBuilder prepareGet(String url);
@@ -166,7 +166,7 @@ public interface AsyncHttpClient extends Closeable {
     /**
      * Prepare an HTTP client CONNECT request.
      *
-     * @param url A well formed URL.
+     * @param url A well-formed URL.
      * @return {@link RequestBuilder}
      */
     BoundRequestBuilder prepareConnect(String url);
@@ -174,7 +174,7 @@ public interface AsyncHttpClient extends Closeable {
     /**
      * Prepare an HTTP client OPTIONS request.
      *
-     * @param url A well formed URL.
+     * @param url A well-formed URL.
      * @return {@link RequestBuilder}
      */
     BoundRequestBuilder prepareOptions(String url);
@@ -182,7 +182,7 @@ public interface AsyncHttpClient extends Closeable {
     /**
      * Prepare an HTTP client HEAD request.
      *
-     * @param url A well formed URL.
+     * @param url A well-formed URL.
      * @return {@link RequestBuilder}
      */
     BoundRequestBuilder prepareHead(String url);
@@ -190,7 +190,7 @@ public interface AsyncHttpClient extends Closeable {
     /**
      * Prepare an HTTP client POST request.
      *
-     * @param url A well formed URL.
+     * @param url A well-formed URL.
      * @return {@link RequestBuilder}
      */
     BoundRequestBuilder preparePost(String url);
@@ -198,7 +198,7 @@ public interface AsyncHttpClient extends Closeable {
     /**
      * Prepare an HTTP client PUT request.
      *
-     * @param url A well formed URL.
+     * @param url A well-formed URL.
      * @return {@link RequestBuilder}
      */
     BoundRequestBuilder preparePut(String url);
@@ -206,7 +206,7 @@ public interface AsyncHttpClient extends Closeable {
     /**
      * Prepare an HTTP client DELETE request.
      *
-     * @param url A well formed URL.
+     * @param url A well-formed URL.
      * @return {@link RequestBuilder}
      */
     BoundRequestBuilder prepareDelete(String url);
@@ -214,7 +214,7 @@ public interface AsyncHttpClient extends Closeable {
     /**
      * Prepare an HTTP client PATCH request.
      *
-     * @param url A well formed URL.
+     * @param url A well-formed URL.
      * @return {@link RequestBuilder}
      */
     BoundRequestBuilder preparePatch(String url);
@@ -222,7 +222,7 @@ public interface AsyncHttpClient extends Closeable {
     /**
      * Prepare an HTTP client TRACE request.
      *
-     * @param url A well formed URL.
+     * @param url A well-formed URL.
      * @return {@link RequestBuilder}
      */
     BoundRequestBuilder prepareTrace(String url);
diff --git a/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java
index 972fa0b3a..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}
@@ -231,7 +234,7 @@ public interface AsyncHttpClientConfig {
     boolean isDisableUrlEncodingForBoundRequests();
 
     /**
-     * @return true if AHC is to use a LAX cookie encoder, eg accept illegal chars in cookie value
+     * @return true if AHC is to use a LAX cookie encoder, e.g. accept illegal chars in cookie value
      */
     boolean isUseLaxCookieEncoder();
 
@@ -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 28cb0b293..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.
@@ -20,7 +20,7 @@
 import java.util.Objects;
 
 /**
- * A record class representing the state of an (@link org.asynchttpclient.AsyncHttpClient).
+ * A record class representing the state of a (@link org.asynchttpclient.AsyncHttpClient).
  */
 public class ClientStats {
 
diff --git a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java
index ab841fa28..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.exception.FilterException;
 import org.asynchttpclient.filter.FilterContext;
-import org.asynchttpclient.filter.FilterException;
 import org.asynchttpclient.filter.RequestFilter;
 import org.asynchttpclient.handler.resumable.ResumableAsyncHandler;
 import org.asynchttpclient.netty.channel.ChannelManager;
@@ -40,7 +40,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Predicate;
 
-import static org.asynchttpclient.util.Assertions.assertNotNull;
+import static java.util.Objects.requireNonNull;
 import static org.asynchttpclient.util.HttpConstants.Methods.CONNECT;
 import static org.asynchttpclient.util.HttpConstants.Methods.DELETE;
 import static org.asynchttpclient.util.HttpConstants.Methods.GET;
@@ -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();
                 }
@@ -294,7 +294,7 @@ private  ListenableFuture execute(Request request, final AsyncHandler a
     private  FilterContext preProcessRequest(FilterContext fc) throws FilterException {
         for (RequestFilter asyncFilter : config.getRequestFilters()) {
             fc = asyncFilter.filter(fc);
-            assertNotNull(fc, "filterContext");
+            requireNonNull(fc, "filterContext");
         }
 
         Request request = fc.getRequest();
diff --git a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java
index 059cc3e78..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();
@@ -978,7 +998,7 @@ public Builder setStrict302Handling(final boolean strict302Handling) {
 
         /**
          * If true, AHC will  add Accept-Encoding HTTP header to each request
-         *
+         * 

* If false (default), AHC will either leave AcceptEncoding header as is * (if enableAutomaticDecompression is false) or will remove unsupported * algorithms (if enableAutomaticDecompression is true) @@ -988,7 +1008,6 @@ public Builder setCompressionEnforced(boolean compressionEnforced) { return this; } - /* * If true (default), AHC will add a Netty HttpContentDecompressor, so compressed * content will automatically get decompressed. @@ -997,7 +1016,7 @@ public Builder setCompressionEnforced(boolean compressionEnforced) { * be done by calling code. */ public Builder setEnableAutomaticDecompression(boolean enable) { - this.enableAutomaticDecompression = enable; + enableAutomaticDecompression = enable; return this; } @@ -1070,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; @@ -1435,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/HttpResponseStatus.java b/client/src/main/java/org/asynchttpclient/HttpResponseStatus.java index 60c82908e..8ac5c316d 100644 --- a/client/src/main/java/org/asynchttpclient/HttpResponseStatus.java +++ b/client/src/main/java/org/asynchttpclient/HttpResponseStatus.java @@ -21,7 +21,7 @@ import java.net.SocketAddress; /** - * A class that represent the HTTP response' status line (code + text) + * A class that represent the HTTP response status line (code + text) */ public abstract class HttpResponseStatus { diff --git a/client/src/main/java/org/asynchttpclient/ListenableFuture.java b/client/src/main/java/org/asynchttpclient/ListenableFuture.java index 930d8d8c2..6f9280369 100755 --- a/client/src/main/java/org/asynchttpclient/ListenableFuture.java +++ b/client/src/main/java/org/asynchttpclient/ListenableFuture.java @@ -70,7 +70,7 @@ public interface ListenableFuture extends Future { * in the thread where completion happens. *
* There is no guaranteed ordering of execution of listeners, they may get - * called in the order they were added and they may get called out of order, + * called in the order they were added, and they may get called out of order, * but any listener added through this method is guaranteed to be called once * the computation is complete. * 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/Realm.java b/client/src/main/java/org/asynchttpclient/Realm.java index 2006bd8b8..c6b70a7de 100644 --- a/client/src/main/java/org/asynchttpclient/Realm.java +++ b/client/src/main/java/org/asynchttpclient/Realm.java @@ -29,7 +29,7 @@ import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.asynchttpclient.util.Assertions.assertNotNull; +import static java.util.Objects.requireNonNull; import static org.asynchttpclient.util.HttpConstants.Methods.GET; import static org.asynchttpclient.util.MessageDigestUtils.pooledMd5MessageDigest; import static org.asynchttpclient.util.MiscUtils.isNonEmpty; @@ -92,7 +92,7 @@ private Realm(@Nullable AuthScheme scheme, @Nullable Map customLoginConfig, @Nullable String loginContextName) { - this.scheme = assertNotNull(scheme, "scheme"); + this.scheme = requireNonNull(scheme, "scheme"); this.principal = principal; this.password = password; this.realmName = realmName; diff --git a/client/src/main/java/org/asynchttpclient/Request.java b/client/src/main/java/org/asynchttpclient/Request.java index 0f8cdaa06..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 @@ -84,34 +87,45 @@ public interface Request { List getCookies(); /** - * @return the request's body byte array (only non null if it was set this way) + * @return the request's body byte array (only non-null if it was set this way) */ byte @Nullable [] getByteData(); /** - * @return the request's body array of byte arrays (only non null if it was set this way) + * @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) + * @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) + * @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 InputStream (only non null if it was set this way) + * @return the request's body ByteBuf (only non-null if it was set this way) */ - @Nullable InputStream getStreamData(); + @Nullable + ByteBuf getByteBufData(); /** - * @return the request's body BodyGenerator (only non null if it was set this way) + * @return the request's body InputStream (only non-null if it was set this way) */ - @Nullable BodyGenerator getBodyGenerator(); + @Nullable + InputStream getStreamData(); + + /** + * @return the request's body BodyGenerator (only non-null if it was set this way) + */ + @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". @@ -159,7 +178,7 @@ public interface Request { Duration getRequestTimeout(); /** - * @return the read timeout. Non zero values means "override config value". + * @return the read timeout. Non-zero values means "override config value". */ Duration getReadTimeout(); @@ -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 @@ -186,7 +206,6 @@ public interface Request { /** * @return a new request builder using this request as a prototype */ - @SuppressWarnings("deprecation") default RequestBuilder toBuilder() { return new RequestBuilder(this); } diff --git a/client/src/main/java/org/asynchttpclient/RequestBuilder.java b/client/src/main/java/org/asynchttpclient/RequestBuilder.java index fed545a6f..9b4491ffc 100644 --- a/client/src/main/java/org/asynchttpclient/RequestBuilder.java +++ b/client/src/main/java/org/asynchttpclient/RequestBuilder.java @@ -39,16 +39,7 @@ public RequestBuilder(String method, boolean disableUrlEncoding, boolean validat super(method, disableUrlEncoding, validateHeaders); } - /** - * @deprecated Use request.toBuilder() instead - */ - @Deprecated - public RequestBuilder(Request prototype) { + RequestBuilder(Request prototype) { super(prototype); } - - @Deprecated - public RequestBuilder(Request prototype, boolean disableUrlEncoding, boolean validateHeaders) { - super(prototype, disableUrlEncoding, validateHeaders); - } } diff --git a/client/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/client/src/main/java/org/asynchttpclient/RequestBuilderBase.java index 0be0a8f44..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())) { @@ -226,7 +229,7 @@ public T addHeader(CharSequence name, String value) { } /** - * Add a header value for the request. If a header with {@code name} was setup for this request already - + * Add a header value for the request. If a header with {@code name} was set up for this request already - * call will add one more header value and convert it to multi-value header * * @param name header name @@ -244,7 +247,7 @@ public T addHeader(CharSequence name, Object value) { } /** - * Add header values for the request. If a header with {@code name} was setup for this request already - + * Add header values for the request. If a header with {@code name} was set up for this request already - * call will add more header values and convert it to multi-value header * * @param name header name @@ -267,7 +270,7 @@ public T setHeaders(HttpHeaders headers) { /** * Set request headers using a map {@code headers} of pair (Header name, Header values) - * This method could be used to setup multi-valued headers + * This method could be used to set up multivalued headers * * @param headers map of header names as the map keys and header values {@link Iterable} as the map values * @return {@code this} @@ -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; @@ -559,7 +584,7 @@ private RequestBuilderBase executeSignatureCalculator() { // build a first version of the request, without signatureCalculator in play RequestBuilder rb = new RequestBuilder(method); - // make copy of mutable collections so we don't risk affecting + // make copy of mutable collections, so we don't risk affecting // original RequestBuilder // call setFormParams first as it resets other fields if (formParams != null) { @@ -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/SignatureCalculator.java b/client/src/main/java/org/asynchttpclient/SignatureCalculator.java index 0341b6b1f..98000f0d2 100644 --- a/client/src/main/java/org/asynchttpclient/SignatureCalculator.java +++ b/client/src/main/java/org/asynchttpclient/SignatureCalculator.java @@ -25,6 +25,7 @@ */ @FunctionalInterface public interface SignatureCalculator { + /** * Method called when {@link RequestBuilder#build} method is called. * Should first calculate signature information and then modify request 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 d4469360d..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 @@ -50,7 +51,7 @@ public interface ChannelPool { boolean removeAll(Channel channel); /** - * Return true if a channel can be cached. A implementation can decide based + * Return true if a channel can be cached. An implementation can decide based * on some rules to allow caching Calling this method is equivalent of * checking the returned value of {@link ChannelPool#offer(Channel, Object)} * diff --git a/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java b/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java index 1e910df03..324a4ce34 100644 --- a/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java +++ b/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. + * Copyright (c) 2014-2024 AsyncHttpClient Project. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,8 +65,7 @@ class CompositePartitionKey { private final int proxyPort; private final @Nullable ProxyType proxyType; - CompositePartitionKey(String targetHostBaseUrl, @Nullable String virtualHost, - @Nullable String proxyHost, int proxyPort, @Nullable ProxyType proxyType) { + CompositePartitionKey(String targetHostBaseUrl, @Nullable String virtualHost, @Nullable String proxyHost, int proxyPort, @Nullable ProxyType proxyType) { this.targetHostBaseUrl = targetHostBaseUrl; this.virtualHost = virtualHost; this.proxyHost = proxyHost; @@ -102,12 +101,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = targetHostBaseUrl != null ? targetHostBaseUrl.hashCode() : 0; - result = 31 * result + (virtualHost != null ? virtualHost.hashCode() : 0); - result = 31 * result + (proxyHost != null ? proxyHost.hashCode() : 0); - result = 31 * result + proxyPort; - result = 31 * result + (proxyType != null ? proxyType.hashCode() : 0); - return result; + return Objects.hash(targetHostBaseUrl, virtualHost, proxyHost, proxyPort, proxyType); } @Override 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 8ea39e6bd..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. @@ -22,38 +22,62 @@ import java.util.Map; import java.util.function.Predicate; +/** + * A {@link ChannelPool} implementation that doesn't pool anything. + */ public enum NoopChannelPool implements ChannelPool { INSTANCE; + /** + * @return always false since this is a {@link NoopChannelPool} + */ @Override public boolean offer(Channel channel, Object partitionKey) { return false; } + /** + * @return always null since this is a {@link NoopChannelPool} + */ @Override public @Nullable Channel poll(Object partitionKey) { return null; } + /** + * @return always false since this is a {@link NoopChannelPool} + */ @Override public boolean removeAll(Channel channel) { return false; } + /** + * @return always true since this is a {@link NoopChannelPool} + */ @Override public boolean isOpen() { return true; } + /** + * Does nothing since this is a {@link NoopChannelPool} + */ @Override public void destroy() { } + /** + * Does nothing since this is a {@link NoopChannelPool} + */ @Override public void flushPartitions(Predicate predicate) { } + /** + * @return always {@link Collections#emptyMap()} since this is a {@link NoopChannelPool} + */ @Override public Map getIdleChannelCountPerHost() { return Collections.emptyMap(); 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/config/AsyncHttpClientConfigHelper.java b/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigHelper.java index 9d5800227..7bb87afb3 100644 --- a/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigHelper.java +++ b/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigHelper.java @@ -25,7 +25,8 @@ public final class AsyncHttpClientConfigHelper { - private static volatile @Nullable Config config; + @Nullable + private static volatile Config config; private AsyncHttpClientConfigHelper() { } @@ -63,18 +64,29 @@ public void reload() { propsCache.clear(); } + /** + * Parse a property file. + * + * @param file the file to parse + * @param required if true, the file must be present + * @return the parsed properties + * @throws RuntimeException if the file is required and not present or if the file can't be parsed + */ private Properties parsePropertiesFile(String file, boolean required) { Properties props = new Properties(); - InputStream is = getClass().getResourceAsStream(file); - if (is != null) { - try { - props.load(is); - } catch (IOException e) { - throw new IllegalArgumentException("Can't parse config file " + file, e); + try (InputStream is = getClass().getResourceAsStream(file)) { + if (is != null) { + try { + props.load(is); + } catch (IOException e) { + throw new IllegalArgumentException("Can't parse config file " + file, e); + } + } else if (required) { + throw new IllegalArgumentException("Can't locate config file " + file); } - } else if (required) { - throw new IllegalArgumentException("Can't locate config file " + file); + } catch (IOException e) { + throw new RuntimeException(e); } return props; @@ -93,7 +105,8 @@ public String getString(String key) { }); } - public @Nullable String[] getStringArray(String key) { + @Nullable + public String[] getStringArray(String key) { String s = getString(key); s = s.trim(); if (s.isEmpty()) { diff --git a/client/src/main/java/org/asynchttpclient/cookie/CookieStore.java b/client/src/main/java/org/asynchttpclient/cookie/CookieStore.java index 225516c11..d19a0d13e 100644 --- a/client/src/main/java/org/asynchttpclient/cookie/CookieStore.java +++ b/client/src/main/java/org/asynchttpclient/cookie/CookieStore.java @@ -34,20 +34,21 @@ * @since 2.1 */ public interface CookieStore extends Counted { + /** * Adds one {@link Cookie} to the store. This is called for every incoming HTTP response. * If the given cookie has already expired it will not be added. * - *

A cookie to store may or may not be associated with an URI. If it - * is not associated with an URI, the cookie's domain and path attribute - * will indicate where it comes from. If it is associated with an URI and + *

A cookie to store may or may not be associated with a URI. If it + * is not associated with a URI, the cookie's domain and path attribute + * will indicate where it comes from. If it is associated with a URI and * its domain and path attribute are not specified, given URI will indicate * where this cookie comes from. * *

If a cookie corresponding to the given URI already exists, * then it is replaced with the new one. * - * @param uri the {@link Uri uri} this cookie associated with. if {@code null}, this cookie will not be associated with an URI + * @param uri the {@link Uri uri} this cookie associated with. if {@code null}, this cookie will not be associated with a URI * @param cookie the {@link Cookie cookie} to be added */ void add(Uri uri, Cookie cookie); diff --git a/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java b/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java index d14392fb4..5832185cc 100644 --- a/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java +++ b/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java @@ -17,7 +17,6 @@ import io.netty.handler.codec.http.cookie.Cookie; import org.asynchttpclient.uri.Uri; -import org.asynchttpclient.util.Assertions; import org.asynchttpclient.util.MiscUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -34,6 +33,8 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import static java.util.Objects.requireNonNull; + public final class ThreadSafeCookieStore implements CookieStore { private final Map> cookieJar = new ConcurrentHashMap<>(); @@ -54,8 +55,7 @@ public List get(Uri uri) { @Override public List getAll() { - return cookieJar - .values() + return cookieJar.values() .stream() .flatMap(map -> map.values().stream()) .filter(pair -> !hasCookieExpired(pair.cookie, pair.createdAt)) @@ -89,7 +89,6 @@ public void evictExpired() { removeExpired(); } - @Override public int incrementAndGet() { return counter.incrementAndGet(); @@ -227,8 +226,11 @@ private List getStoredCookies(String domain, String path, boolean secure private void removeExpired() { final boolean[] removed = {false}; - cookieJar.values().forEach(cookieMap -> removed[0] |= cookieMap.entrySet().removeIf( - v -> hasCookieExpired(v.getValue().cookie, v.getValue().createdAt))); + + cookieJar.values() + .forEach(cookieMap -> removed[0] |= cookieMap.entrySet() + .removeIf(v -> hasCookieExpired(v.getValue().cookie, v.getValue().createdAt))); + if (removed[0]) { cookieJar.entrySet().removeIf(entry -> entry.getValue() == null || entry.getValue().isEmpty()); } @@ -244,11 +246,12 @@ private static class CookieKey implements Comparable { } @Override - public int compareTo(@NotNull CookieKey o) { - Assertions.assertNotNull(o, "Parameter can't be null"); + public int compareTo(@NotNull CookieKey cookieKey) { + requireNonNull(cookieKey, "Parameter can't be null"); + int result; - if ((result = name.compareTo(o.name)) == 0) { - result = path.compareTo(o.path); + if ((result = name.compareTo(cookieKey.name)) == 0) { + result = path.compareTo(cookieKey.path); } return result; } @@ -260,10 +263,7 @@ public boolean equals(Object obj) { @Override public int hashCode() { - int result = 17; - result = 31 * result + name.hashCode(); - result = 31 * result + path.hashCode(); - return result; + return Objects.hash(name, path); } @Override diff --git a/client/src/main/java/org/asynchttpclient/exception/ChannelClosedException.java b/client/src/main/java/org/asynchttpclient/exception/ChannelClosedException.java index 1d3325503..4b63997aa 100644 --- a/client/src/main/java/org/asynchttpclient/exception/ChannelClosedException.java +++ b/client/src/main/java/org/asynchttpclient/exception/ChannelClosedException.java @@ -19,9 +19,12 @@ import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace; -@SuppressWarnings("serial") +/** + * This exception is thrown when a channel is closed. + */ public final class ChannelClosedException extends IOException { + private static final long serialVersionUID = -2528693697240456658L; public static final ChannelClosedException INSTANCE = unknownStackTrace(new ChannelClosedException(), ChannelClosedException.class, "INSTANCE"); private ChannelClosedException() { diff --git a/client/src/main/java/org/asynchttpclient/exception/FilterException.java b/client/src/main/java/org/asynchttpclient/exception/FilterException.java new file mode 100644 index 000000000..6e5902b9b --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/exception/FilterException.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 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.exception; + +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.filter.RequestFilter; +import org.asynchttpclient.filter.ResponseFilter; + +/** + * An exception that can be thrown by an {@link AsyncHandler} to interrupt invocation of + * the {@link RequestFilter} and {@link ResponseFilter}. It also interrupts the request and response processing. + */ +public class FilterException extends Exception { + + private static final long serialVersionUID = -3963344749394925069L; + + public FilterException(final String message) { + super(message); + } + + public FilterException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/client/src/main/java/org/asynchttpclient/exception/PoolAlreadyClosedException.java b/client/src/main/java/org/asynchttpclient/exception/PoolAlreadyClosedException.java index 168a11a09..d66c3b76a 100644 --- a/client/src/main/java/org/asynchttpclient/exception/PoolAlreadyClosedException.java +++ b/client/src/main/java/org/asynchttpclient/exception/PoolAlreadyClosedException.java @@ -19,9 +19,12 @@ import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace; -@SuppressWarnings("serial") +/** + * This exception is thrown when a channel pool is already closed. + */ public class PoolAlreadyClosedException extends IOException { + private static final long serialVersionUID = -3883404852005245296L; public static final PoolAlreadyClosedException INSTANCE = unknownStackTrace(new PoolAlreadyClosedException(), PoolAlreadyClosedException.class, "INSTANCE"); private PoolAlreadyClosedException() { diff --git a/client/src/main/java/org/asynchttpclient/exception/RemotelyClosedException.java b/client/src/main/java/org/asynchttpclient/exception/RemotelyClosedException.java index 6ea0bd334..2b1977a6f 100644 --- a/client/src/main/java/org/asynchttpclient/exception/RemotelyClosedException.java +++ b/client/src/main/java/org/asynchttpclient/exception/RemotelyClosedException.java @@ -19,9 +19,12 @@ import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace; -@SuppressWarnings("serial") +/** + * This exception is thrown when a channel is closed by remote host. + */ public final class RemotelyClosedException extends IOException { + private static final long serialVersionUID = 5634105738124356785L; public static final RemotelyClosedException INSTANCE = unknownStackTrace(new RemotelyClosedException(), RemotelyClosedException.class, "INSTANCE"); private RemotelyClosedException() { diff --git a/client/src/main/java/org/asynchttpclient/exception/TooManyConnectionsException.java b/client/src/main/java/org/asynchttpclient/exception/TooManyConnectionsException.java index ec9351152..6797c68c7 100644 --- a/client/src/main/java/org/asynchttpclient/exception/TooManyConnectionsException.java +++ b/client/src/main/java/org/asynchttpclient/exception/TooManyConnectionsException.java @@ -17,8 +17,11 @@ import java.io.IOException; -@SuppressWarnings("serial") +/** + * This exception is thrown when too many connections are opened. + */ public class TooManyConnectionsException extends IOException { + private static final long serialVersionUID = 8645586459539317237L; public TooManyConnectionsException(int max) { super("Too many connections: " + max); diff --git a/client/src/main/java/org/asynchttpclient/exception/TooManyConnectionsPerHostException.java b/client/src/main/java/org/asynchttpclient/exception/TooManyConnectionsPerHostException.java index 5c5d7926f..e7959bb37 100644 --- a/client/src/main/java/org/asynchttpclient/exception/TooManyConnectionsPerHostException.java +++ b/client/src/main/java/org/asynchttpclient/exception/TooManyConnectionsPerHostException.java @@ -17,9 +17,13 @@ import java.io.IOException; -@SuppressWarnings("serial") +/** + * This exception is thrown when too many connections are opened to a remote host. + */ public class TooManyConnectionsPerHostException extends IOException { + private static final long serialVersionUID = 5702859695179937503L; + public TooManyConnectionsPerHostException(int max) { super("Too many connections: " + max); } diff --git a/client/src/main/java/org/asynchttpclient/filter/FilterException.java b/client/src/main/java/org/asynchttpclient/filter/FilterException.java deleted file mode 100644 index 8d209211a..000000000 --- a/client/src/main/java/org/asynchttpclient/filter/FilterException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.filter; - -import org.asynchttpclient.AsyncHandler; - -/** - * An exception that can be thrown by an {@link AsyncHandler} to interrupt invocation of - * the {@link RequestFilter} and {@link ResponseFilter}. It also interrupt the request and response processing. - */ -@SuppressWarnings("serial") -public class FilterException extends Exception { - - public FilterException(final String message) { - super(message); - } - - public FilterException(final String message, final Throwable cause) { - super(message, cause); - } -} diff --git a/client/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java b/client/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java index a7df37717..182ea56e2 100644 --- a/client/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java +++ b/client/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java @@ -14,11 +14,12 @@ import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.Request; +import org.asynchttpclient.exception.FilterException; import java.io.IOException; /** - * This filter is invoked when an {@link IOException} occurs during an http transaction. + * This filter is invoked when an {@link IOException} occurs during a http transaction. */ public interface IOExceptionFilter { diff --git a/client/src/main/java/org/asynchttpclient/filter/RequestFilter.java b/client/src/main/java/org/asynchttpclient/filter/RequestFilter.java index 8b2a6fd9d..d4eaf8a32 100644 --- a/client/src/main/java/org/asynchttpclient/filter/RequestFilter.java +++ b/client/src/main/java/org/asynchttpclient/filter/RequestFilter.java @@ -13,6 +13,7 @@ package org.asynchttpclient.filter; import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.exception.FilterException; /** * A Filter interface that gets invoked before making an actual request. diff --git a/client/src/main/java/org/asynchttpclient/filter/ResponseFilter.java b/client/src/main/java/org/asynchttpclient/filter/ResponseFilter.java index 3fd9ffb23..939adf6d8 100644 --- a/client/src/main/java/org/asynchttpclient/filter/ResponseFilter.java +++ b/client/src/main/java/org/asynchttpclient/filter/ResponseFilter.java @@ -13,6 +13,7 @@ package org.asynchttpclient.filter; import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.exception.FilterException; /** * A Filter interface that gets invoked before making the processing of the response bytes. {@link ResponseFilter} are invoked diff --git a/client/src/main/java/org/asynchttpclient/filter/ThrottleRequestFilter.java b/client/src/main/java/org/asynchttpclient/filter/ThrottleRequestFilter.java index 9b5225198..3d5d9943a 100644 --- a/client/src/main/java/org/asynchttpclient/filter/ThrottleRequestFilter.java +++ b/client/src/main/java/org/asynchttpclient/filter/ThrottleRequestFilter.java @@ -12,6 +12,7 @@ */ package org.asynchttpclient.filter; +import org.asynchttpclient.exception.FilterException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/client/src/main/java/org/asynchttpclient/handler/BodyDeferringAsyncHandler.java b/client/src/main/java/org/asynchttpclient/handler/BodyDeferringAsyncHandler.java index 72f7a648a..dc58fc2c5 100644 --- a/client/src/main/java/org/asynchttpclient/handler/BodyDeferringAsyncHandler.java +++ b/client/src/main/java/org/asynchttpclient/handler/BodyDeferringAsyncHandler.java @@ -37,9 +37,9 @@ * long as headers are received, and return Response as soon as possible, but * still pouring response body into supplied output stream. This handler is * meant for situations when the "recommended" way (using - * {@code client.prepareGet("http://foo.com/aResource").execute().get()} + * {@code client.prepareGet("http://foo.com/aResource").execute().get()}), which * would not work for you, since a potentially large response body is about to - * be GETted, but you need headers first, or you don't know yet (depending on + * be GET-ted, but you need headers first, or you don't know yet (depending on * some logic, maybe coming from headers) where to save the body, or you just * want to leave body stream to some other component to consume it. *
@@ -211,7 +211,7 @@ protected void closeOut() throws IOException { * 1st cached, probably incomplete one. Note: the response returned by this * method will contain everything except the response body itself, * so invoking any method like Response.getResponseBodyXXX() will result in - * error! Also, please not that this method might return {@code null} + * error! Also, please note that this method might return {@code null} * in case of some errors. * * @return a {@link Response} 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/handler/ProgressAsyncHandler.java b/client/src/main/java/org/asynchttpclient/handler/ProgressAsyncHandler.java index 50100e3bf..04839e276 100644 --- a/client/src/main/java/org/asynchttpclient/handler/ProgressAsyncHandler.java +++ b/client/src/main/java/org/asynchttpclient/handler/ProgressAsyncHandler.java @@ -25,7 +25,7 @@ public interface ProgressAsyncHandler extends AsyncHandler { /** - * Invoked when the content (a {@link File}, {@link String} or {@link FileInputStream} has been fully + * Invoked when the content (a {@link File}, {@link String} or {@link FileInputStream}) has been fully * written on the I/O socket. * * @return a {@link AsyncHandler.State} telling to CONTINUE or ABORT the current processing. @@ -33,7 +33,7 @@ public interface ProgressAsyncHandler extends AsyncHandler { State onHeadersWritten(); /** - * Invoked when the content (a {@link File}, {@link String} or {@link FileInputStream} has been fully + * Invoked when the content (a {@link File}, {@link String} or {@link FileInputStream}) has been fully * written on the I/O socket. * * @return a {@link AsyncHandler.State} telling to CONTINUE or ABORT the current processing. diff --git a/client/src/main/java/org/asynchttpclient/handler/TransferListener.java b/client/src/main/java/org/asynchttpclient/handler/TransferListener.java index c3921f1a0..fd50dcf7e 100644 --- a/client/src/main/java/org/asynchttpclient/handler/TransferListener.java +++ b/client/src/main/java/org/asynchttpclient/handler/TransferListener.java @@ -15,7 +15,7 @@ import io.netty.handler.codec.http.HttpHeaders; /** - * A simple interface an application can implements in order to received byte transfer information. + * A simple interface an application can implement in order to received byte transfer information. */ public interface TransferListener { diff --git a/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java index a52c52f5a..6b8794547 100644 --- a/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java +++ b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java @@ -37,8 +37,8 @@ import static io.netty.handler.codec.http.HttpHeaderNames.RANGE; /** - * An {@link AsyncHandler} which support resumable download, e.g when used with an {@link ResumableIOExceptionFilter}, - * this handler can resume the download operation at the point it was before the interruption occurred. This prevent having to + * An {@link AsyncHandler} which support resumable download, e.g. when used with an {@link ResumableIOExceptionFilter}, + * this handler can resume the download operation at the point it was before the interruption occurred. This prevents having to * download the entire file again. It's the responsibility of the {@link ResumableAsyncHandler} * to track how many bytes has been transferred and to properly adjust the file's write position. *
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 d8f68b0d7..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. @@ -23,7 +23,7 @@ import java.net.SocketAddress; /** - * A class that represent the HTTP response' status line (code + text) + * A class that represent the HTTP response status line (code + text) */ public class NettyResponseStatus extends HttpResponseStatus { 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 45ff3adc1..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. @@ -113,7 +113,7 @@ public class ChannelManager { private boolean isInstanceof(Object object, String name) { final Class clazz; try { - clazz = Class.forName(name, false, this.getClass().getClassLoader()); + clazz = Class.forName(name, false, getClass().getClassLoader()); } catch (ClassNotFoundException ignored) { return false; } diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/ChannelState.java b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelState.java index 6dd824466..1b550983e 100644 --- a/client/src/main/java/org/asynchttpclient/netty/channel/ChannelState.java +++ b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelState.java @@ -16,5 +16,23 @@ package org.asynchttpclient.netty.channel; public enum ChannelState { - NEW, POOLED, RECONNECTED, CLOSED, + /** + * The channel is new + */ + NEW, + + /** + * The channel is open and pooled + */ + POOLED, + + /** + * The channel is reconnected + */ + RECONNECTED, + + /** + * The channel is closed + */ + CLOSED, } 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 2c1916faf..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. @@ -42,7 +42,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -import static org.asynchttpclient.util.Assertions.assertNotNull; +import static java.util.Objects.requireNonNull; import static org.asynchttpclient.util.DateUtils.unpreciseMillisTime; /** @@ -260,7 +260,7 @@ private static final class IdleChannel { private volatile int owned; IdleChannel(Channel channel, long start) { - this.channel = assertNotNull(channel, "channel"); + this.channel = requireNonNull(channel, "channel"); this.start = start; } 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 a3218dbbf..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. @@ -38,8 +38,12 @@ private static boolean exceptionInMethod(Throwable t, String className, String m private static boolean recoverOnConnectCloseException(Throwable t) { while (true) { - if (exceptionInMethod(t, "sun.nio.ch.SocketChannelImpl", "checkConnect")) return true; - if (t.getCause() == null) return false; + if (exceptionInMethod(t, "sun.nio.ch.SocketChannelImpl", "checkConnect")) { + return true; + } + if (t.getCause() == null) { + return false; + } t = t.getCause(); } } @@ -67,7 +71,9 @@ public static boolean recoverOnReadOrWriteException(Throwable t) { } catch (Throwable ignore) { } - if (t.getCause() == null) return false; + if (t.getCause() == null) { + return false; + } t = t.getCause(); } } 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 85f96d2f7..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. @@ -179,17 +179,8 @@ public void channelActive(ChannelHandlerContext ctx) { @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.read(); -// if (!isHandledByReactiveStreams(ctx)) { -// ctx.read(); -// } else { -// ctx.fireChannelReadComplete(); -// } } -// private static boolean isHandledByReactiveStreams(ChannelHandlerContext ctx) { -// return Channels.getAttribute(ctx.channel()) instanceof StreamedResponsePublisher; -// } - void finishUpdate(NettyResponseFuture future, Channel channel, boolean close) { future.cancelTimeouts(); 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 2c7cb3b4f..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. @@ -132,7 +132,7 @@ public void handleRead(Channel channel, NettyResponseFuture future, Object e) webSocket.handleFrame(frame); } else { // WebSocket hasn't been opened yet, but upgrading the pipeline triggered a read and a frame was sent along the HTTP upgrade response - // as we want to keep sequential order (but can't notify user of open before upgrading so he doesn't to try send immediately), we have to buffer + // as we want to keep sequential order (but can't notify user of open before upgrading, so he doesn't try to send immediately), we have to buffer webSocket.bufferFrame(frame); } diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ProxyUnauthorized407Interceptor.java b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ProxyUnauthorized407Interceptor.java index d937ff8cf..b30f6bbd9 100644 --- a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ProxyUnauthorized407Interceptor.java +++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ProxyUnauthorized407Interceptor.java @@ -205,7 +205,7 @@ private static void ntlmProxyChallenge(String authenticateHeader, HttpHeaders re future.setInProxyAuth(false); } else { String serverChallenge = authenticateHeader.substring("NTLM ".length()).trim(); - String challengeHeader = NtlmEngine.INSTANCE.generateType3Msg(proxyRealm.getPrincipal(), proxyRealm.getPassword(), proxyRealm.getNtlmDomain(), + String challengeHeader = NtlmEngine.generateType3Msg(proxyRealm.getPrincipal(), proxyRealm.getPassword(), proxyRealm.getNtlmDomain(), proxyRealm.getNtlmHost(), serverChallenge); // FIXME we might want to filter current NTLM and add (leave other // Authorization headers untouched) 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 89f70f50a..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.exception.FilterException; import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; import org.asynchttpclient.filter.ResponseFilter; import org.asynchttpclient.netty.NettyResponseFuture; import org.asynchttpclient.netty.request.NettyRequestSender; @@ -50,7 +50,7 @@ public boolean exitAfterProcessingFilters(Channel channel, try { fc = asyncFilter.filter(fc); // FIXME Is it worth protecting against this? -// assertNotNull(fc, "filterContext"); +// requireNonNull(fc, "filterContext"); } catch (FilterException fe) { requestSender.abort(channel, future, fe); } diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Unauthorized401Interceptor.java b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Unauthorized401Interceptor.java index 3f3c710aa..cb89f70b8 100644 --- a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Unauthorized401Interceptor.java +++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Unauthorized401Interceptor.java @@ -179,7 +179,7 @@ private static void ntlmChallenge(String authenticateHeader, NettyResponseFuture future) { if ("NTLM".equals(authenticateHeader)) { - // server replied bare NTLM => we didn't preemptively sent Type1Msg + // server replied bare NTLM => we didn't preemptively send Type1Msg String challengeHeader = NtlmEngine.INSTANCE.generateType1Msg(); // FIXME we might want to filter current NTLM and add (leave other // Authorization headers untouched) @@ -188,7 +188,7 @@ private static void ntlmChallenge(String authenticateHeader, } else { String serverChallenge = authenticateHeader.substring("NTLM ".length()).trim(); - String challengeHeader = NtlmEngine.INSTANCE.generateType3Msg(realm.getPrincipal(), realm.getPassword(), + String challengeHeader = NtlmEngine.generateType3Msg(realm.getPrincipal(), realm.getPassword(), realm.getNtlmDomain(), realm.getNtlmHost(), serverChallenge); // FIXME we might want to filter current NTLM and add (leave other // Authorization headers untouched) 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 317c4522f..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. @@ -17,6 +17,8 @@ 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; @@ -31,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; @@ -65,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; @@ -95,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())) { @@ -173,13 +179,26 @@ public NettyRequest newNettyRequest(Request request, boolean performConnectReque String userDefinedAcceptEncoding = headers.get(ACCEPT_ENCODING); if (userDefinedAcceptEncoding != null) { if (config.isEnableAutomaticDecompression()) { - // we don't support Brotli ATM, for automatic decompression. - // For manual decompression by user, any encoding may suite, so leave untouched - headers.set(ACCEPT_ENCODING, filterOutBrotliFromAcceptEncoding(userDefinedAcceptEncoding)); + if (!Brotli.isAvailable()) { + // Brotli is not available. + // 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 headers.set(ACCEPT_ENCODING, GZIP_DEFLATE); + 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 fd4a0e19b..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.filter.FilterException; import org.asynchttpclient.filter.IOExceptionFilter; import org.asynchttpclient.handler.TransferCompletionHandler; import org.asynchttpclient.netty.NettyResponseFuture; @@ -67,7 +67,7 @@ import static io.netty.handler.codec.http.HttpHeaderNames.EXPECT; import static java.util.Collections.singletonList; -import static org.asynchttpclient.util.Assertions.assertNotNull; +import static java.util.Objects.requireNonNull; import static org.asynchttpclient.util.AuthenticatorUtils.perConnectionAuthorizationHeader; import static org.asynchttpclient.util.AuthenticatorUtils.perConnectionProxyAuthorizationHeader; import static org.asynchttpclient.util.HttpConstants.Methods.CONNECT; @@ -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,15 +113,13 @@ 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 return sendRequestWithCertainForceConnect(request, asyncHandler, future, proxyServer, true); } else { - // CONNECT will depend if we can pool or connection or if we have to open a new one + // CONNECT will depend on if we can pool or connection or if we have to open a new one return sendRequestThroughProxy(request, asyncHandler, future, proxyServer); } } else { @@ -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,16 +144,24 @@ 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); + } } /** - * Using CONNECT depends on wither we can fetch a valid channel or not Loop - * until we get a valid channel from the pool and it's still valid once the + * Using CONNECT depends on whether we can fetch a valid channel or not Loop + * until we get a valid channel from the pool, and it's still valid once the * request is built @ */ private ListenableFuture sendRequestThroughProxy(Request request, @@ -506,7 +521,7 @@ public boolean applyIoExceptionFiltersAndReplayRequest(NettyResponseFuture fu for (IOExceptionFilter asyncFilter : config.getIoExceptionFilters()) { try { fc = asyncFilter.filter(fc); - assertNotNull(fc, "filterContext"); + requireNonNull(fc, "filterContext"); } catch (FilterException efe) { abort(channel, future, efe); } 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 e6dbd8c7e..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. @@ -21,7 +21,7 @@ import io.netty.handler.stream.ChunkedInput; import org.asynchttpclient.request.body.Body; -import static org.asynchttpclient.util.Assertions.assertNotNull; +import static java.util.Objects.requireNonNull; /** * Adapts a {@link Body} to Netty's {@link ChunkedInput}. @@ -37,7 +37,7 @@ public class BodyChunkedInput implements ChunkedInput { private long progress; BodyChunkedInput(Body body) { - this.body = assertNotNull(body, "body"); + this.body = requireNonNull(body, "body"); contentLength = body.getContentLength(); if (contentLength <= 0) { chunkSize = DEFAULT_CHUNK_SIZE; 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 5542f4dd5..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. @@ -22,7 +22,7 @@ import java.io.IOException; import java.nio.channels.WritableByteChannel; -import static org.asynchttpclient.util.Assertions.assertNotNull; +import static java.util.Objects.requireNonNull; import static org.asynchttpclient.util.MiscUtils.closeSilently; /** @@ -34,7 +34,7 @@ class BodyFileRegion extends AbstractReferenceCounted implements FileRegion { private long transferred; BodyFileRegion(RandomAccessBody body) { - this.body = assertNotNull(body, "body"); + this.body = requireNonNull(body, "body"); } @Override 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 740683efd..c2338c46a 100644 --- a/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java +++ b/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java @@ -116,7 +116,7 @@ public final class NtlmEngine { * username and the result of encrypting the nonce sent by the server using * the user's password as the key. * - * @param user The user name. This should not include the domain name. + * @param user The username. This should not include the domain name. * @param password The password. * @param host The host that is originating the authentication request. * @param domain The domain to authenticate within. @@ -766,7 +766,7 @@ private static void oddParity(final byte[] bytes) { * NTLM message generation, base class */ private static class NTLMMessage { - private static final byte[] EMPTY_BYTE_ARRAY = new byte[]{}; + private static final byte[] EMPTY_BYTE_ARRAY = {}; /** * The current response */ @@ -846,14 +846,14 @@ protected final void readBytes(final byte[] buffer, final int position) { } /** - * Read a ushort from a position within the message buffer + * Read an ushort from a position within the message buffer */ protected int readUShort(final int position) { return NtlmEngine.readUShort(messageContents, position); } /** - * Read a ulong from a position within the message buffer + * Read an ulong from a position within the message buffer */ protected final int readULong(final int position) { return NtlmEngine.readULong(messageContents, position); @@ -1033,7 +1033,7 @@ static class Type2Message extends NTLMMessage { // Next 2 bytes, major/minor version number (e.g. 0x05 0x02) // Next 8 bytes, build number // Next 2 bytes, protocol version number (e.g. 0x00 0x0f) - // Next, various text fields, and a ushort of value 0 at the end + // Next, various text fields, and an ushort of value 0 at the end // Parse out the rest of the info we need from the message // The nonce is the 8 bytes starting from the byte in position 24. @@ -1083,7 +1083,8 @@ byte[] getChallenge() { /** * Retrieve the target */ - @Nullable String getTarget() { + @Nullable + String getTarget() { return target; } @@ -1566,7 +1567,7 @@ void update(final byte[] input) { /** * Creates the first message (type 1 message) in the NTLM authentication - * sequence. This message includes the user name, domain and host for the + * sequence. This message includes the username, domain and host for the * authentication session. * * @return String the message to add to the HTTP request header. diff --git a/client/src/main/java/org/asynchttpclient/oauth/ConsumerKey.java b/client/src/main/java/org/asynchttpclient/oauth/ConsumerKey.java deleted file mode 100644 index a86eb35a5..000000000 --- a/client/src/main/java/org/asynchttpclient/oauth/ConsumerKey.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2018-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.oauth; - -import org.asynchttpclient.util.Utf8UrlEncoder; - -/** - * Value class for OAuth consumer keys. - */ -public class ConsumerKey { - private final String key; - private final String secret; - private final String percentEncodedKey; - - public ConsumerKey(String key, String secret) { - this.key = key; - this.secret = secret; - percentEncodedKey = Utf8UrlEncoder.percentEncodeQueryElement(key); - } - - public String getKey() { - return key; - } - - public String getSecret() { - return secret; - } - - public String getPercentEncodedKey() { - return percentEncodedKey; - } -} diff --git a/client/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java b/client/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java deleted file mode 100644 index 3f1a1a7c5..000000000 --- a/client/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2016-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.oauth; - -import io.netty.handler.codec.http.HttpHeaderNames; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilderBase; -import org.asynchttpclient.SignatureCalculator; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -/** - * OAuth {@link SignatureCalculator} that delegates to {@link OAuthSignatureCalculatorInstance}s. - */ -public class OAuthSignatureCalculator implements SignatureCalculator { - - private static final ThreadLocal INSTANCES = ThreadLocal.withInitial(() -> { - try { - return new OAuthSignatureCalculatorInstance(); - } catch (NoSuchAlgorithmException e) { - throw new ExceptionInInitializerError(e); - } - }); - - private final ConsumerKey consumerAuth; - - private final RequestToken userAuth; - - /** - * @param consumerAuth Consumer key to use for signature calculation - * @param userAuth Request/access token to use for signature calculation - */ - public OAuthSignatureCalculator(ConsumerKey consumerAuth, RequestToken userAuth) { - this.consumerAuth = consumerAuth; - this.userAuth = userAuth; - } - - @Override - public void calculateAndAddSignature(Request request, RequestBuilderBase requestBuilder) { - try { - String authorization = INSTANCES.get().computeAuthorizationHeader( - consumerAuth, - userAuth, - request.getUri(), - request.getMethod(), - request.getFormParams(), - request.getQueryParams()); - requestBuilder.setHeader(HttpHeaderNames.AUTHORIZATION, authorization); - } catch (InvalidKeyException e) { - throw new IllegalArgumentException("Failed to compute a valid key from consumer and user secrets", e); - } - } -} diff --git a/client/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculatorInstance.java b/client/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculatorInstance.java deleted file mode 100644 index ec7da244f..000000000 --- a/client/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculatorInstance.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2017-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.oauth; - -import org.asynchttpclient.Param; -import org.asynchttpclient.SignatureCalculator; -import org.asynchttpclient.uri.Uri; -import org.asynchttpclient.util.StringBuilderPool; -import org.asynchttpclient.util.StringUtils; -import org.asynchttpclient.util.Utf8UrlEncoder; -import org.jetbrains.annotations.Nullable; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.nio.ByteBuffer; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; -import java.util.regex.Pattern; - -import static java.nio.charset.StandardCharsets.UTF_8; - -/** - * Non thread-safe {@link SignatureCalculator} for OAuth1. - *

- * Supports most common signature inclusion and calculation methods: HMAC-SHA1 for calculation, and Header inclusion as inclusion method. Nonce generation uses simple random - * numbers with base64 encoding. - */ -public class OAuthSignatureCalculatorInstance { - - private static final Pattern STAR_CHAR_PATTERN = Pattern.compile("*", Pattern.LITERAL); - private static final Pattern PLUS_CHAR_PATTERN = Pattern.compile("+", Pattern.LITERAL); - private static final Pattern ENCODED_TILDE_PATTERN = Pattern.compile("%7E", Pattern.LITERAL); - private static final String KEY_OAUTH_CONSUMER_KEY = "oauth_consumer_key"; - private static final String KEY_OAUTH_NONCE = "oauth_nonce"; - private static final String KEY_OAUTH_SIGNATURE = "oauth_signature"; - private static final String KEY_OAUTH_SIGNATURE_METHOD = "oauth_signature_method"; - private static final String KEY_OAUTH_TIMESTAMP = "oauth_timestamp"; - private static final String KEY_OAUTH_TOKEN = "oauth_token"; - private static final String KEY_OAUTH_VERSION = "oauth_version"; - private static final String OAUTH_VERSION_1_0 = "1.0"; - private static final String OAUTH_SIGNATURE_METHOD = "HMAC-SHA1"; - private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; - - private final Mac mac; - private final byte[] nonceBuffer = new byte[16]; - private final Parameters parameters = new Parameters(); - - public OAuthSignatureCalculatorInstance() throws NoSuchAlgorithmException { - mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); - } - - public String computeAuthorizationHeader(ConsumerKey consumerAuth, RequestToken userAuth, Uri uri, String method, List formParams, List queryParams) - throws InvalidKeyException { - String nonce = generateNonce(); - long timestamp = generateTimestamp(); - return computeAuthorizationHeader(consumerAuth, userAuth, uri, method, formParams, queryParams, timestamp, nonce); - } - - private String generateNonce() { - ThreadLocalRandom.current().nextBytes(nonceBuffer); - // let's use base64 encoding over hex, slightly more compact than hex or decimals - return Base64.getEncoder().encodeToString(nonceBuffer); - } - - private static long generateTimestamp() { - return System.currentTimeMillis() / 1000L; - } - - String computeAuthorizationHeader(ConsumerKey consumerAuth, RequestToken userAuth, Uri uri, String method, - List formParams, List queryParams, long timestamp, String nonce) throws InvalidKeyException { - String percentEncodedNonce = Utf8UrlEncoder.percentEncodeQueryElement(nonce); - String signature = computeSignature(consumerAuth, userAuth, uri, method, formParams, queryParams, timestamp, percentEncodedNonce); - return computeAuthorizationHeader(consumerAuth, userAuth, signature, timestamp, percentEncodedNonce); - } - - String computeSignature(ConsumerKey consumerAuth, RequestToken userAuth, Uri uri, String method, - List formParams, List queryParams, long oauthTimestamp, String percentEncodedNonce) throws InvalidKeyException { - StringBuilder sb = signatureBaseString( - consumerAuth, - userAuth, - uri, - method, - formParams, - queryParams, - oauthTimestamp, - percentEncodedNonce); - - ByteBuffer rawBase = StringUtils.charSequence2ByteBuffer(sb, UTF_8); - byte[] rawSignature = digest(consumerAuth, userAuth, rawBase); - // and finally, base64 encoded... phew! - return Base64.getEncoder().encodeToString(rawSignature); - } - - StringBuilder signatureBaseString(ConsumerKey consumerAuth, RequestToken userAuth, Uri uri, String method, - List formParams, List queryParams, long oauthTimestamp, String percentEncodedNonce) { - - // beware: must generate first as we're using pooled StringBuilder - String baseUrl = uri.toBaseUrl(); - String encodedParams = encodedParams(consumerAuth, userAuth, oauthTimestamp, percentEncodedNonce, formParams, queryParams); - - StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); - sb.append(method); // POST / GET etc (nothing to URL encode) - sb.append('&'); - Utf8UrlEncoder.encodeAndAppendPercentEncoded(sb, baseUrl); - - // and all that needs to be URL encoded (... again!) - sb.append('&'); - Utf8UrlEncoder.encodeAndAppendPercentEncoded(sb, encodedParams); - return sb; - } - - private String encodedParams(ConsumerKey consumerAuth, RequestToken userAuth, long oauthTimestamp, String percentEncodedNonce, - List formParams, List queryParams) { - parameters.reset(); - - // List of all query and form parameters added to this request; needed for calculating request signature - // Start with standard OAuth parameters we need - parameters.add(KEY_OAUTH_CONSUMER_KEY, consumerAuth.getPercentEncodedKey()) - .add(KEY_OAUTH_NONCE, percentEncodedNonce) - .add(KEY_OAUTH_SIGNATURE_METHOD, OAUTH_SIGNATURE_METHOD) - .add(KEY_OAUTH_TIMESTAMP, String.valueOf(oauthTimestamp)); - if (userAuth.getKey() != null) { - parameters.add(KEY_OAUTH_TOKEN, userAuth.getPercentEncodedKey()); - } - parameters.add(KEY_OAUTH_VERSION, OAUTH_VERSION_1_0); - - if (formParams != null) { - for (Param param : formParams) { - // formParams are not already encoded - String value = param.getValue() != null ? Utf8UrlEncoder.percentEncodeQueryElement(param.getValue()) : ""; - parameters.add(Utf8UrlEncoder.percentEncodeQueryElement(param.getName()), value); - } - } - if (queryParams != null) { - for (Param param : queryParams) { - // queryParams are already form-url-encoded - // but OAuth1 uses RFC3986_UNRESERVED_CHARS so * and + have to be encoded - parameters.add(percentEncodeAlreadyFormUrlEncoded(param.getName()), percentEncodeAlreadyFormUrlEncoded(param.getValue())); - } - } - return parameters.sortAndConcat(); - } - - private static String percentEncodeAlreadyFormUrlEncoded(@Nullable String s) { - if (s == null) { - return ""; - } - s = STAR_CHAR_PATTERN.matcher(s).replaceAll("%2A"); - s = PLUS_CHAR_PATTERN.matcher(s).replaceAll("%20"); - s = ENCODED_TILDE_PATTERN.matcher(s).replaceAll("~"); - return s; - } - - private byte[] digest(ConsumerKey consumerAuth, RequestToken userAuth, ByteBuffer message) throws InvalidKeyException { - StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); - Utf8UrlEncoder.encodeAndAppendQueryElement(sb, consumerAuth.getSecret()); - sb.append('&'); - if (userAuth != null && userAuth.getSecret() != null) { - Utf8UrlEncoder.encodeAndAppendQueryElement(sb, userAuth.getSecret()); - } - byte[] keyBytes = StringUtils.charSequence2Bytes(sb, UTF_8); - SecretKeySpec signingKey = new SecretKeySpec(keyBytes, HMAC_SHA1_ALGORITHM); - - mac.init(signingKey); - mac.update(message); - return mac.doFinal(); - } - - String computeAuthorizationHeader(ConsumerKey consumerAuth, RequestToken userAuth, String signature, long oauthTimestamp, String percentEncodedNonce) { - StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); - sb.append("OAuth "); - sb.append(KEY_OAUTH_CONSUMER_KEY).append("=\"").append(consumerAuth.getPercentEncodedKey()).append("\", "); - if (userAuth.getKey() != null) { - sb.append(KEY_OAUTH_TOKEN).append("=\"").append(userAuth.getPercentEncodedKey()).append("\", "); - } - sb.append(KEY_OAUTH_SIGNATURE_METHOD).append("=\"").append(OAUTH_SIGNATURE_METHOD).append("\", "); - - // careful: base64 has chars that need URL encoding: - sb.append(KEY_OAUTH_SIGNATURE).append("=\""); - Utf8UrlEncoder.encodeAndAppendPercentEncoded(sb, signature).append("\", "); - sb.append(KEY_OAUTH_TIMESTAMP).append("=\"").append(oauthTimestamp).append("\", "); - - sb.append(KEY_OAUTH_NONCE).append("=\"").append(percentEncodedNonce).append("\", "); - - sb.append(KEY_OAUTH_VERSION).append("=\"").append(OAUTH_VERSION_1_0).append('"'); - return sb.toString(); - } -} diff --git a/client/src/main/java/org/asynchttpclient/oauth/Parameter.java b/client/src/main/java/org/asynchttpclient/oauth/Parameter.java deleted file mode 100644 index 3fa651a47..000000000 --- a/client/src/main/java/org/asynchttpclient/oauth/Parameter.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2017-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.oauth; - -/** - * Helper class for sorting query and form parameters that we need - */ -final class Parameter implements Comparable { - - final String key, value; - - Parameter(String key, String value) { - this.key = key; - this.value = value; - } - - @Override - public int compareTo(Parameter other) { - int keyDiff = key.compareTo(other.key); - return keyDiff == 0 ? value.compareTo(other.value) : keyDiff; - } - - @Override - public String toString() { - return key + '=' + value; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - Parameter parameter = (Parameter) o; - return key.equals(parameter.key) && value.equals(parameter.value); - } - - @Override - public int hashCode() { - int result = key.hashCode(); - result = 31 * result + value.hashCode(); - return result; - } -} diff --git a/client/src/main/java/org/asynchttpclient/oauth/Parameters.java b/client/src/main/java/org/asynchttpclient/oauth/Parameters.java deleted file mode 100644 index f8ba4b2a3..000000000 --- a/client/src/main/java/org/asynchttpclient/oauth/Parameters.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2017-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.oauth; - -import org.asynchttpclient.util.StringBuilderPool; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -final class Parameters { - - private final List parameters = new ArrayList<>(); - - public Parameters add(String key, String value) { - parameters.add(new Parameter(key, value)); - return this; - } - - public void reset() { - parameters.clear(); - } - - String sortAndConcat() { - // then sort them (AFTER encoding, important) - Collections.sort(parameters); - - // and build parameter section using pre-encoded pieces: - StringBuilder encodedParams = StringBuilderPool.DEFAULT.stringBuilder(); - for (Parameter param : parameters) { - encodedParams.append(param.key).append('=').append(param.value).append('&'); - } - int length = encodedParams.length(); - if (length > 0) { - encodedParams.setLength(length - 1); - } - return encodedParams.toString(); - } -} diff --git a/client/src/main/java/org/asynchttpclient/oauth/RequestToken.java b/client/src/main/java/org/asynchttpclient/oauth/RequestToken.java deleted file mode 100644 index fe90fd413..000000000 --- a/client/src/main/java/org/asynchttpclient/oauth/RequestToken.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2017-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.oauth; - -import org.asynchttpclient.util.Utf8UrlEncoder; - -/** - * Value class used for OAuth tokens (request secret, access secret); - * simple container with two parts, public id part ("key") and - * confidential ("secret") part. - */ -public class RequestToken { - private final String key; - private final String secret; - private final String percentEncodedKey; - - public RequestToken(String key, String token) { - this.key = key; - secret = token; - percentEncodedKey = Utf8UrlEncoder.percentEncodeQueryElement(key); - } - - public String getKey() { - return key; - } - - public String getSecret() { - return secret; - } - - public String getPercentEncodedKey() { - return percentEncodedKey; - } -} diff --git a/client/src/main/java/org/asynchttpclient/proxy/ProxyServer.java b/client/src/main/java/org/asynchttpclient/proxy/ProxyServer.java index c7d1d6106..9cb33362c 100644 --- a/client/src/main/java/org/asynchttpclient/proxy/ProxyServer.java +++ b/client/src/main/java/org/asynchttpclient/proxy/ProxyServer.java @@ -26,7 +26,7 @@ import java.util.List; import java.util.function.Function; -import static org.asynchttpclient.util.Assertions.assertNotNull; +import static java.util.Objects.requireNonNull; import static org.asynchttpclient.util.MiscUtils.isNonEmpty; /** @@ -100,7 +100,7 @@ public ProxyType getProxyType() { * Properties */ public boolean isIgnoredForHost(String hostname) { - assertNotNull(hostname, "hostname"); + requireNonNull(hostname, "hostname"); if (isNonEmpty(nonProxyHosts)) { for (String nonProxyHost : nonProxyHosts) { if (matchNonProxyHost(hostname, nonProxyHost)) { 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/BodyGenerator.java b/client/src/main/java/org/asynchttpclient/request/body/generator/BodyGenerator.java index 7c5027d94..835ef7bd6 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/generator/BodyGenerator.java +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/BodyGenerator.java @@ -23,7 +23,7 @@ public interface BodyGenerator { /** * Creates a new instance of the request body to be read. While each invocation of this method is supposed to create * a fresh instance of the body, the actual contents of all these body instances is the same. For example, the body - * needs to be resend after an authentication challenge of a redirect. + * needs to be resent after an authentication challenge of a redirect. * * @return The request body, never {@code null}. */ 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/FileBodyGenerator.java b/client/src/main/java/org/asynchttpclient/request/body/generator/FileBodyGenerator.java index ae8869486..82bc02111 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/generator/FileBodyGenerator.java +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/FileBodyGenerator.java @@ -16,7 +16,7 @@ import java.io.File; -import static org.asynchttpclient.util.Assertions.assertNotNull; +import static java.util.Objects.requireNonNull; /** * Creates a request body from the contents of a file. @@ -32,7 +32,7 @@ public FileBodyGenerator(File file) { } public FileBodyGenerator(File file, long regionSeek, long regionLength) { - this.file = assertNotNull(file, "file"); + this.file = requireNonNull(file, "file"); this.regionLength = regionLength; this.regionSeek = regionSeek; } diff --git a/client/src/main/java/org/asynchttpclient/request/body/generator/InputStreamBodyGenerator.java b/client/src/main/java/org/asynchttpclient/request/body/generator/InputStreamBodyGenerator.java index b98460b69..1f602dae4 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/generator/InputStreamBodyGenerator.java +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/InputStreamBodyGenerator.java @@ -24,7 +24,7 @@ * A {@link BodyGenerator} which use an {@link InputStream} for reading bytes, without having to read the entire stream in memory. *
* NOTE: The {@link InputStream} must support the {@link InputStream#mark} and {@link InputStream#reset()} operation. If not, mechanisms like authentication, redirect, or - * resumable download will not works. + * resumable download will not work. */ public final class InputStreamBodyGenerator implements BodyGenerator { 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 6ef4b8d79..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. @@ -17,7 +17,7 @@ import java.nio.charset.Charset; -import static org.asynchttpclient.util.Assertions.assertNotNull; +import static java.util.Objects.requireNonNull; public class ByteArrayPart extends FileLikePart { @@ -45,7 +45,7 @@ public ByteArrayPart(String name, byte[] bytes, String contentType, Charset char public ByteArrayPart(String name, byte[] bytes, String contentType, Charset charset, String fileName, String contentId, String transferEncoding) { super(name, contentType, charset, fileName, contentId, transferEncoding); - this.bytes = assertNotNull(bytes, "bytes"); + this.bytes = requireNonNull(bytes, "bytes"); } public byte[] getBytes() { 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/InputStreamPart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/InputStreamPart.java index 048105973..aa14c979a 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/multipart/InputStreamPart.java +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/InputStreamPart.java @@ -18,7 +18,7 @@ import java.io.InputStream; import java.nio.charset.Charset; -import static org.asynchttpclient.util.Assertions.assertNotNull; +import static java.util.Objects.requireNonNull; public class InputStreamPart extends FileLikePart { @@ -48,7 +48,7 @@ public InputStreamPart(String name, InputStream inputStream, String fileName, lo public InputStreamPart(String name, InputStream inputStream, String fileName, long contentLength, String contentType, Charset charset, String contentId, String transferEncoding) { super(name, contentType, charset, fileName, contentId, transferEncoding); - this.inputStream = assertNotNull(inputStream, "inputStream"); + this.inputStream = requireNonNull(inputStream, "inputStream"); this.contentLength = contentLength; } diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/MultipartBody.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/MultipartBody.java index 4100fc128..a1fbb6087 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/multipart/MultipartBody.java +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/MultipartBody.java @@ -28,7 +28,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -import static org.asynchttpclient.util.Assertions.assertNotNull; +import static java.util.Objects.requireNonNull; import static org.asynchttpclient.util.MiscUtils.closeSilently; public class MultipartBody implements RandomAccessBody { @@ -46,7 +46,7 @@ public class MultipartBody implements RandomAccessBody { public MultipartBody(List> parts, String contentType, byte[] boundary) { this.boundary = boundary; this.contentType = contentType; - this.parts = assertNotNull(parts, "parts"); + this.parts = requireNonNull(parts, "parts"); contentLength = computeContentLength(); } diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/MultipartUtils.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/MultipartUtils.java index 8f55fa2ae..eb7314f4b 100644 --- a/client/src/main/java/org/asynchttpclient/request/body/multipart/MultipartUtils.java +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/MultipartUtils.java @@ -29,7 +29,7 @@ import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; import static java.nio.charset.StandardCharsets.US_ASCII; -import static org.asynchttpclient.util.Assertions.assertNotNull; +import static java.util.Objects.requireNonNull; import static org.asynchttpclient.util.HttpUtils.computeMultipartBoundary; import static org.asynchttpclient.util.HttpUtils.patchContentTypeWithBoundaryAttribute; import static org.asynchttpclient.util.MiscUtils.isNonEmpty; @@ -48,7 +48,7 @@ private MultipartUtils() { * @return a MultipartBody */ public static MultipartBody newMultipartBody(List parts, HttpHeaders requestHeaders) { - assertNotNull(parts, "parts"); + requireNonNull(parts, "parts"); byte[] boundary; String contentType; 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 4f853bfb9..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. @@ -18,7 +18,7 @@ import java.nio.charset.Charset; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.asynchttpclient.util.Assertions.assertNotNull; +import static java.util.Objects.requireNonNull; import static org.asynchttpclient.util.MiscUtils.withDefault; public class StringPart extends PartBase { @@ -51,7 +51,7 @@ public StringPart(String name, String value, String contentType, Charset charset public StringPart(String name, String value, String contentType, Charset charset, String contentId, String transferEncoding) { super(name, contentType, charsetOrDefault(charset), contentId, transferEncoding); - assertNotNull(value, "value"); + requireNonNull(value, "value"); // See RFC 2048, 2.8. "8bit Data" if (value.indexOf(0) != -1) { 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 b0bad47ce..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. @@ -17,7 +17,7 @@ import org.jetbrains.annotations.Nullable; -import static org.asynchttpclient.util.Assertions.assertNotNull; +import static java.util.Objects.requireNonNull; import static org.asynchttpclient.util.MiscUtils.isNonEmpty; final class UriParser { @@ -187,8 +187,8 @@ private void computeUserInfo(String nonNullAuthority) { } } - private boolean isMaybeIPV6(String nonNullHost) { - // If the host is surrounded by [ and ] then its an IPv6 + private static boolean isMaybeIPV6(String nonNullHost) { + // If the host is surrounded by [ and ] then it's an IPv6 // literal address as specified in RFC2732 return nonNullHost.length() > 0 && nonNullHost.charAt(0) == '['; } @@ -369,7 +369,7 @@ private void parse(@Nullable Uri context) { } public static UriParser parse(@Nullable Uri context, final String originalUrl) { - assertNotNull(originalUrl, "originalUrl"); + requireNonNull(originalUrl, "originalUrl"); final UriParser parser = new UriParser(originalUrl); parser.parse(context); return parser; diff --git a/client/src/main/java/org/asynchttpclient/util/Assertions.java b/client/src/main/java/org/asynchttpclient/util/Assertions.java index 5a5f29c06..0b2e38a7c 100644 --- a/client/src/main/java/org/asynchttpclient/util/Assertions.java +++ b/client/src/main/java/org/asynchttpclient/util/Assertions.java @@ -18,23 +18,16 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; +import static java.util.Objects.requireNonNull; + public final class Assertions { private Assertions() { } - @Contract(value = "null, _ -> fail", pure = true) - public static T assertNotNull(@Nullable T value, String name) { - if (value == null) { - throw new NullPointerException(name); - } - return value; - - } - @Contract(value = "null, _ -> fail", pure = true) public static String assertNotEmpty(@Nullable String value, String name) { - assertNotNull(value, name); + requireNonNull(value, name); if (value.length() == 0) { throw new IllegalArgumentException("empty " + name); } diff --git a/client/src/main/java/org/asynchttpclient/util/EnsuresNonNull.java b/client/src/main/java/org/asynchttpclient/util/EnsuresNonNull.java index a0cecaf8e..64ccee659 100644 --- a/client/src/main/java/org/asynchttpclient/util/EnsuresNonNull.java +++ b/client/src/main/java/org/asynchttpclient/util/EnsuresNonNull.java @@ -11,7 +11,7 @@ * all class's fields defined in "param" will be @NotNull. */ @Retention(RetentionPolicy.CLASS) -@Target({ElementType.METHOD}) +@Target(ElementType.METHOD) public @interface EnsuresNonNull { String[] value(); } 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/MiscUtils.java b/client/src/main/java/org/asynchttpclient/util/MiscUtils.java index 12506b9a9..5a37cce75 100644 --- a/client/src/main/java/org/asynchttpclient/util/MiscUtils.java +++ b/client/src/main/java/org/asynchttpclient/util/MiscUtils.java @@ -47,12 +47,12 @@ public static boolean isNonEmpty(byte @Nullable [] array) { return array != null && array.length != 0; } - @Contract(value = "null -> false") + @Contract("null -> false") public static boolean isNonEmpty(@Nullable Collection collection) { return collection != null && !collection.isEmpty(); } - @Contract(value = "null -> false") + @Contract("null -> false") public static boolean isNonEmpty(@Nullable Map map) { return map != null && !map.isEmpty(); } diff --git a/client/src/main/java/org/asynchttpclient/util/ProxyUtils.java b/client/src/main/java/org/asynchttpclient/util/ProxyUtils.java index 0942ddd8e..a7bf5b7e2 100644 --- a/client/src/main/java/org/asynchttpclient/util/ProxyUtils.java +++ b/client/src/main/java/org/asynchttpclient/util/ProxyUtils.java @@ -94,7 +94,7 @@ private ProxyUtils() { /** * Creates a proxy server instance from the given properties. - * Currently the default http.* proxy properties are supported as well as properties specific for AHC. + * Currently, the default http.* proxy properties are supported as well as properties specific for AHC. * * @param properties the properties to evaluate. Must not be null. * @return a ProxyServer instance or null, if no valid properties were set. 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/util/Utf8UrlEncoder.java b/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java index f2928fa60..fe01e3208 100644 --- a/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java +++ b/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java @@ -147,7 +147,7 @@ public static StringBuilder encodeAndAppendFormElement(StringBuilder sb, CharSeq return appendEncoded(sb, input, FORM_URL_ENCODED_SAFE_CHARS, true); } - @Contract(value = "!null -> !null") + @Contract("!null -> !null") public static @Nullable String percentEncodeQueryElement(@Nullable String input) { if (input == null) { return null; diff --git a/client/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java b/client/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java deleted file mode 100644 index bf57ddd21..000000000 --- a/client/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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.webdav; - -import io.netty.handler.codec.http.HttpHeaders; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; -import org.asynchttpclient.netty.NettyResponse; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.io.InputStream; -import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Future; - -/** - * Simple {@link AsyncHandler} that add support for WebDav's response manipulation. - * - * @param the result type - */ -public abstract class WebDavCompletionHandlerBase implements AsyncHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(WebDavCompletionHandlerBase.class); - private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY; - private final List bodyParts = Collections.synchronizedList(new ArrayList<>()); - private @Nullable HttpResponseStatus status; - private @Nullable HttpHeaders headers; - - static { - DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); - if (Boolean.getBoolean("org.asynchttpclient.webdav.enableDtd")) { - try { - DOCUMENT_BUILDER_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - } catch (ParserConfigurationException e) { - LOGGER.error("Failed to disable doctype declaration"); - throw new ExceptionInInitializerError(e); - } - } - } - - @Override - public final State onBodyPartReceived(final HttpResponseBodyPart content) { - bodyParts.add(content); - return State.CONTINUE; - } - - @Override - public final State onStatusReceived(final HttpResponseStatus status) { - this.status = status; - return State.CONTINUE; - } - - @Override - public final State onHeadersReceived(final HttpHeaders headers) { - this.headers = headers; - return State.CONTINUE; - } - - private Document readXMLResponse(InputStream stream, HttpResponseStatus initialStatus) { - Document document; - try { - document = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder().parse(stream); - status = parse(document, initialStatus); - } catch (SAXException | IOException | ParserConfigurationException e) { - LOGGER.error(e.getMessage(), e); - throw new RuntimeException(e); - } - return document; - } - - private static HttpResponseStatus parse(Document document, HttpResponseStatus initialStatus) { - HttpResponseStatus status = initialStatus; - Element element = document.getDocumentElement(); - NodeList statusNode = element.getElementsByTagName("status"); - for (int i = 0; i < statusNode.getLength(); i++) { - Node node = statusNode.item(i); - - String value = node.getFirstChild().getNodeValue(); - int statusCode = Integer.parseInt(value.substring(value.indexOf(' '), value.lastIndexOf(' ')).trim()); - String statusText = value.substring(value.lastIndexOf(' ')); - status = new HttpStatusWrapper(status, statusText, statusCode); - } - return status; - } - - @Override - public final T onCompleted() throws Exception { - if (status != null) { - Document document = null; - if (status.getStatusCode() == 207) { - document = readXMLResponse(new NettyResponse(status, headers, bodyParts).getResponseBodyAsStream(), status); - } - // recompute response as readXMLResponse->parse might have updated it - return onCompleted(new WebDavResponse(new NettyResponse(status, headers, bodyParts), document)); - } else { - throw new IllegalStateException("Status is null"); - } - } - - @Override - public void onThrowable(Throwable t) { - LOGGER.debug(t.getMessage(), t); - } - - /** - * Invoked once the HTTP response has been fully read. - * - * @param response The {@link Response} - * @return Type of the value that will be returned by the associated {@link Future} - * @throws Exception if something wrong happens - */ - public abstract T onCompleted(WebDavResponse response) throws Exception; - - private static class HttpStatusWrapper extends HttpResponseStatus { - - private final HttpResponseStatus wrapped; - - private final String statusText; - - private final int statusCode; - - HttpStatusWrapper(HttpResponseStatus wrapper, String statusText, int statusCode) { - super(wrapper.getUri()); - wrapped = wrapper; - this.statusText = statusText; - this.statusCode = statusCode; - } - - @Override - public int getStatusCode() { - return statusText == null ? wrapped.getStatusCode() : statusCode; - } - - @Override - public String getStatusText() { - return statusText == null ? wrapped.getStatusText() : statusText; - } - - @Override - public String getProtocolName() { - return wrapped.getProtocolName(); - } - - @Override - public int getProtocolMajorVersion() { - return wrapped.getProtocolMajorVersion(); - } - - @Override - public int getProtocolMinorVersion() { - return wrapped.getProtocolMinorVersion(); - } - - @Override - public String getProtocolText() { - return wrapped.getStatusText(); - } - - @Override - public SocketAddress getRemoteAddress() { - return wrapped.getRemoteAddress(); - } - - @Override - public SocketAddress getLocalAddress() { - return wrapped.getLocalAddress(); - } - } -} diff --git a/client/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java b/client/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java deleted file mode 100644 index 650212d2e..000000000 --- a/client/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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.webdav; - -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.cookie.Cookie; -import org.asynchttpclient.Response; -import org.asynchttpclient.uri.Uri; -import org.jetbrains.annotations.Nullable; -import org.w3c.dom.Document; - -import java.io.InputStream; -import java.net.SocketAddress; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.List; - -/** - * Customized {@link Response} which add support for getting the response's body as an XML document (@link WebDavResponse#getBodyAsXML} - */ -public class WebDavResponse implements Response { - - private final Response response; - private final @Nullable Document document; - - WebDavResponse(Response response, @Nullable Document document) { - this.response = response; - this.document = document; - } - - @Override - public int getStatusCode() { - return response.getStatusCode(); - } - - @Override - public String getStatusText() { - return response.getStatusText(); - } - - @Override - public byte[] getResponseBodyAsBytes() { - return response.getResponseBodyAsBytes(); - } - - @Override - public ByteBuffer getResponseBodyAsByteBuffer() { - return response.getResponseBodyAsByteBuffer(); - } - - @Override - public InputStream getResponseBodyAsStream() { - return response.getResponseBodyAsStream(); - } - - @Override - public String getResponseBody() { - return response.getResponseBody(); - } - - @Override - public String getResponseBody(Charset charset) { - return response.getResponseBody(charset); - } - - @Override - public Uri getUri() { - return response.getUri(); - } - - @Override - public String getContentType() { - return response.getContentType(); - } - - @Override - public String getHeader(CharSequence name) { - return response.getHeader(name); - } - - @Override - public List getHeaders(CharSequence name) { - return response.getHeaders(name); - } - - @Override - public HttpHeaders getHeaders() { - return response.getHeaders(); - } - - @Override - public boolean isRedirected() { - return response.isRedirected(); - } - - @Override - public List getCookies() { - return response.getCookies(); - } - - @Override - public boolean hasResponseStatus() { - return response.hasResponseStatus(); - } - - @Override - public boolean hasResponseHeaders() { - return response.hasResponseHeaders(); - } - - @Override - public boolean hasResponseBody() { - return response.hasResponseBody(); - } - - @Override - public SocketAddress getRemoteAddress() { - return response.getRemoteAddress(); - } - - @Override - public SocketAddress getLocalAddress() { - return response.getLocalAddress(); - } - - public @Nullable Document getBodyAsXML() { - return document; - } -} diff --git a/client/src/main/java/org/asynchttpclient/ws/WebSocket.java b/client/src/main/java/org/asynchttpclient/ws/WebSocket.java index 4857876a2..0362ba455 100644 --- a/client/src/main/java/org/asynchttpclient/ws/WebSocket.java +++ b/client/src/main/java/org/asynchttpclient/ws/WebSocket.java @@ -57,7 +57,7 @@ public interface WebSocket { * Allows sending a text frame with fragmentation or extension bits. When using fragmentation, the next fragments must be sent with sendContinuationFrame. * * @param payload a text fragment. - * @param finalFragment flag indicating whether or not this is the final fragment + * @param finalFragment flag indicating whether this is the final fragment * @param rsv extension bits, 0 otherwise * @return a future that will be completed once the frame will be actually written on the wire */ @@ -67,7 +67,7 @@ public interface WebSocket { * Allows sending a text frame with fragmentation or extension bits. When using fragmentation, the next fragments must be sent with sendContinuationFrame. * * @param payload a ByteBuf fragment. - * @param finalFragment flag indicating whether or not this is the final fragment + * @param finalFragment flag indicating whether this is the final fragment * @param rsv extension bits, 0 otherwise * @return a future that will be completed once the frame will be actually written on the wire */ @@ -85,7 +85,7 @@ public interface WebSocket { * Allows sending a binary frame with fragmentation or extension bits. When using fragmentation, the next fragments must be sent with sendContinuationFrame. * * @param payload a binary payload - * @param finalFragment flag indicating whether or not this is the last fragment + * @param finalFragment flag indicating whether this is the last fragment * @param rsv extension bits, 0 otherwise * @return a future that will be completed once the frame will be actually written on the wire */ @@ -95,7 +95,7 @@ public interface WebSocket { * Allows sending a binary frame with fragmentation or extension bits. When using fragmentation, the next fragments must be sent with sendContinuationFrame. * * @param payload a ByteBuf payload - * @param finalFragment flag indicating whether or not this is the last fragment + * @param finalFragment flag indicating whether this is the last fragment * @param rsv extension bits, 0 otherwise * @return a future that will be completed once the frame will be actually written on the wire */ @@ -105,7 +105,7 @@ public interface WebSocket { * Send a text continuation frame. The last fragment must have finalFragment set to true. * * @param payload the text fragment - * @param finalFragment flag indicating whether or not this is the last fragment + * @param finalFragment flag indicating whether this is the last fragment * @param rsv extension bits, 0 otherwise * @return a future that will be completed once the frame will be actually written on the wire */ @@ -115,7 +115,7 @@ public interface WebSocket { * Send a binary continuation frame. The last fragment must have finalFragment set to true. * * @param payload the binary fragment - * @param finalFragment flag indicating whether or not this is the last fragment + * @param finalFragment flag indicating whether this is the last fragment * @param rsv extension bits, 0 otherwise * @return a future that will be completed once the frame will be actually written on the wire */ @@ -125,21 +125,21 @@ public interface WebSocket { * Send a continuation frame (those are actually untyped as counterpart must have memorized first fragmented frame type). The last fragment must have finalFragment set to true. * * @param payload a ByteBuf fragment - * @param finalFragment flag indicating whether or not this is the last fragment + * @param finalFragment flag indicating whether this is the last fragment * @param rsv extension bits, 0 otherwise * @return a future that will be completed once the frame will be actually written on the wire */ Future sendContinuationFrame(ByteBuf payload, boolean finalFragment, int rsv); /** - * Send a empty ping frame + * Send an empty ping frame * * @return a future that will be completed once the frame will be actually written on the wire */ Future sendPingFrame(); /** - * Send a ping frame with a byte array payload (limited to 125 bytes or less). + * Send a ping frame with a byte array payload (limited to 125 bytes or fewer). * * @param payload the payload. * @return a future that will be completed once the frame will be actually written on the wire @@ -147,7 +147,7 @@ public interface WebSocket { Future sendPingFrame(byte[] payload); /** - * Send a ping frame with a ByteBuf payload (limited to 125 bytes or less). + * Send a ping frame with a ByteBuf payload (limited to 125 bytes or fewer). * * @param payload the payload. * @return a future that will be completed once the frame will be actually written on the wire @@ -155,14 +155,14 @@ public interface WebSocket { Future sendPingFrame(ByteBuf payload); /** - * Send a empty pong frame + * Send an empty pong frame * * @return a future that will be completed once the frame will be actually written on the wire */ Future sendPongFrame(); /** - * Send a pong frame with a byte array payload (limited to 125 bytes or less). + * Send a pong frame with a byte array payload (limited to 125 bytes or fewer). * * @param payload the payload. * @return a future that will be completed once the frame will be actually written on the wire @@ -170,7 +170,7 @@ public interface WebSocket { Future sendPongFrame(byte[] payload); /** - * Send a pong frame with a ByteBuf payload (limited to 125 bytes or less). + * Send a pong frame with a ByteBuf payload (limited to 125 bytes or fewer). * * @param payload the payload. * @return a future that will be completed once the frame will be actually written on the wire @@ -178,14 +178,14 @@ public interface WebSocket { Future sendPongFrame(ByteBuf payload); /** - * Send a empty close frame. + * Send an empty close frame. * * @return a future that will be completed once the frame will be actually written on the wire */ Future sendCloseFrame(); /** - * Send a empty close frame. + * Send an empty close frame. * * @param statusCode a status code * @param reasonText a reason 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 60de7f58a..2dcfa859d 100644 --- a/client/src/test/java/org/asynchttpclient/AbstractBasicTest.java +++ b/client/src/test/java/org/asynchttpclient/AbstractBasicTest.java @@ -15,19 +15,22 @@ */ package org.asynchttpclient; +import io.github.nettyplus.leakdetector.junit.NettyLeakDetectorExtension; import org.asynchttpclient.test.EchoHandler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.AbstractHandler; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.asynchttpclient.test.TestUtils.addHttpConnector; @TestInstance(TestInstance.Lifecycle.PER_CLASS) +@ExtendWith(NettyLeakDetectorExtension.class) public abstract class AbstractBasicTest { protected static final Logger logger = LoggerFactory.getLogger(AbstractBasicTest.class); protected static final int TIMEOUT = 30; @@ -36,7 +39,7 @@ public abstract class AbstractBasicTest { protected int port1 = -1; protected int port2 = -1; - @BeforeEach + @BeforeAll public void setUpGlobal() throws Exception { server = new Server(); ServerConnector connector1 = addHttpConnector(server); @@ -50,7 +53,7 @@ public void setUpGlobal() throws Exception { logger.info("Local HTTP server started successfully"); } - @AfterEach + @AfterAll public void tearDownGlobal() throws Exception { logger.debug("Shutting down local server: {}", server); 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 4d598d431..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. @@ -15,7 +15,7 @@ */ package org.asynchttpclient; -import io.github.artsok.RepeatedIfExceptionsTest; +import org.junit.jupiter.api.Test; import java.time.Duration; import java.util.List; @@ -34,7 +34,7 @@ public class ClientStatsTest extends AbstractBasicTest { private static final String hostname = "localhost"; - @RepeatedIfExceptionsTest(repeats = 5) + @Test public void testClientStatus() throws Throwable { try (final AsyncHttpClient client = asyncHttpClient(config().setKeepAlive(true).setPooledConnectionIdleTimeout(Duration.ofSeconds(5)))) { final String url = getTargetUrl(); @@ -51,7 +51,7 @@ public void testClientStatus() throws Throwable { .limit(5) .collect(Collectors.toList()); - Thread.sleep(2000 + 1000); + Thread.sleep(2000); final ClientStats activeStats = client.getClientStats(); @@ -63,7 +63,7 @@ public void testClientStatus() throws Throwable { futures.forEach(future -> future.toCompletableFuture().join()); - Thread.sleep(1000 + 1000); + Thread.sleep(1000); final ClientStats idleStats = client.getClientStats(); @@ -79,7 +79,7 @@ public void testClientStatus() throws Throwable { .limit(3) .collect(Collectors.toList()); - Thread.sleep(2000 + 1000); + Thread.sleep(2000); final ClientStats activeCachedStats = client.getClientStats(); @@ -91,7 +91,7 @@ public void testClientStatus() throws Throwable { repeatedFutures.forEach(future -> future.toCompletableFuture().join()); - Thread.sleep(1000 + 1000); + Thread.sleep(1000); final ClientStats idleCachedStats = client.getClientStats(); @@ -101,7 +101,7 @@ public void testClientStatus() throws Throwable { assertEquals(3, idleCachedStats.getTotalConnectionCount()); assertEquals(3, idleCachedStats.getStatsPerHost().get(hostname).getHostConnectionCount()); - Thread.sleep(5000 + 1000); + Thread.sleep(5000); final ClientStats timeoutStats = client.getClientStats(); @@ -113,7 +113,7 @@ public void testClientStatus() throws Throwable { } } - @RepeatedIfExceptionsTest(repeats = 5) + @Test public void testClientStatusNoKeepalive() throws Throwable { try (final AsyncHttpClient client = asyncHttpClient(config().setKeepAlive(false).setPooledConnectionIdleTimeout(Duration.ofSeconds(1)))) { final String url = getTargetUrl(); @@ -130,7 +130,7 @@ public void testClientStatusNoKeepalive() throws Throwable { .limit(5) .collect(Collectors.toList()); - Thread.sleep(2000 + 1000); + Thread.sleep(2000); final ClientStats activeStats = client.getClientStats(); @@ -142,7 +142,7 @@ public void testClientStatusNoKeepalive() throws Throwable { futures.forEach(future -> future.toCompletableFuture().join()); - Thread.sleep(1000 + 1000); + Thread.sleep(1000); final ClientStats idleStats = client.getClientStats(); @@ -158,7 +158,7 @@ public void testClientStatusNoKeepalive() throws Throwable { .limit(3) .collect(Collectors.toList()); - Thread.sleep(2000 + 1000); + Thread.sleep(2000); final ClientStats activeCachedStats = client.getClientStats(); @@ -170,7 +170,7 @@ public void testClientStatusNoKeepalive() throws Throwable { repeatedFutures.forEach(future -> future.toCompletableFuture().join()); - Thread.sleep(1000 + 1000); + Thread.sleep(1000); final ClientStats idleCachedStats = client.getClientStats(); 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/NettyConnectionResetByPeerTest.java b/client/src/test/java/org/asynchttpclient/netty/NettyConnectionResetByPeerTest.java index a315017a1..484b074a3 100644 --- a/client/src/test/java/org/asynchttpclient/netty/NettyConnectionResetByPeerTest.java +++ b/client/src/test/java/org/asynchttpclient/netty/NettyConnectionResetByPeerTest.java @@ -16,11 +16,13 @@ package org.asynchttpclient.netty; import io.github.artsok.RepeatedIfExceptionsTest; +import io.github.nettyplus.leakdetector.junit.NettyLeakDetectorExtension; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.DefaultAsyncHttpClient; import org.asynchttpclient.DefaultAsyncHttpClientConfig; import org.asynchttpclient.RequestBuilder; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import java.io.IOException; import java.io.InputStream; @@ -34,6 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertInstanceOf; +@ExtendWith(NettyLeakDetectorExtension.class) public class NettyConnectionResetByPeerTest { private String resettingServerAddress; diff --git a/client/src/test/java/org/asynchttpclient/netty/NettyTest.java b/client/src/test/java/org/asynchttpclient/netty/NettyTest.java new file mode 100644 index 000000000..f80c0911e --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/netty/NettyTest.java @@ -0,0 +1,56 @@ +package org.asynchttpclient.netty; + +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; +import org.junit.jupiter.api.condition.OS; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class NettyTest { + @Test + @EnabledOnOs(OS.LINUX) + public void epollIsAvailableOnLinux() { + assertTrue(Epoll.isAvailable()); + } + + @Test + @EnabledOnOs(OS.LINUX) + public void ioUringIsAvailableOnLinux() { + assertTrue(IOUring.isAvailable()); + } + + @Test + @EnabledOnOs(OS.MAC) + public void kqueueIsAvailableOnMac() { + assertTrue(KQueue.isAvailable()); + } + + @Test + @EnabledOnOs(OS.LINUX) + public void brotliIsAvailableOnLinux() { + assertTrue(Brotli.isAvailable()); + } + + @Test + @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/oauth/OAuthSignatureCalculatorTest.java b/client/src/test/java/org/asynchttpclient/oauth/OAuthSignatureCalculatorTest.java deleted file mode 100644 index b48094aa5..000000000 --- a/client/src/test/java/org/asynchttpclient/oauth/OAuthSignatureCalculatorTest.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright (c) 2016-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.oauth; - -import io.github.artsok.RepeatedIfExceptionsTest; -import org.asynchttpclient.Param; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.util.Utf8UrlEncoder; -import org.junit.jupiter.api.Test; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static io.netty.handler.codec.http.HttpHeaderNames.AUTHORIZATION; -import static org.asynchttpclient.Dsl.get; -import static org.asynchttpclient.Dsl.post; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Tests the OAuth signature behavior. - *

- * See Signature Tester for an online oauth signature checker. - */ -public class OAuthSignatureCalculatorTest { - private static final String TOKEN_KEY = "nnch734d00sl2jdk"; - private static final String TOKEN_SECRET = "pfkkdhi9sl3r4s00"; - private static final String NONCE = "kllo9940pd9333jh"; - private static final long TIMESTAMP = 1191242096; - private static final String CONSUMER_KEY = "dpf43f3p2l4k3l03"; - private static final String CONSUMER_SECRET = "kd94hf93k423kf44"; - - // sample from RFC https://tools.ietf.org/html/rfc5849#section-3.4.1 - private static void testSignatureBaseString(Request request) throws NoSuchAlgorithmException { - String signatureBaseString = new OAuthSignatureCalculatorInstance() - .signatureBaseString(// - new ConsumerKey("9djdj82h48djs9d2", CONSUMER_SECRET), - new RequestToken("kkk9d7dh3k39sjv7", TOKEN_SECRET), - request.getUri(), - request.getMethod(), - request.getFormParams(), - request.getQueryParams(), - 137131201, - "7d8f3e4a").toString(); - - assertEquals("POST&" - + "http%3A%2F%2Fexample.com%2Frequest" - + "&a2%3Dr%2520b%26" - + "a3%3D2%2520q%26" - + "a3%3Da%26" - + "b5%3D%253D%25253D%26" - + "c%2540%3D%26" - + "c2%3D%26" - + "oauth_consumer_key%3D9djdj82h48djs9d2%26" - + "oauth_nonce%3D7d8f3e4a%26" - + "oauth_signature_method%3DHMAC-SHA1%26" - + "oauth_timestamp%3D137131201%26" - + "oauth_token%3Dkkk9d7dh3k39sjv7%26" - + "oauth_version%3D1.0", signatureBaseString); - } - - // fork above test with an OAuth token that requires encoding - private static void testSignatureBaseStringWithEncodableOAuthToken(Request request) throws NoSuchAlgorithmException { - String signatureBaseString = new OAuthSignatureCalculatorInstance() - .signatureBaseString(// - new ConsumerKey("9djdj82h48djs9d2", CONSUMER_SECRET), - new RequestToken("kkk9d7dh3k39sjv7", TOKEN_SECRET), - request.getUri(), - request.getMethod(), - request.getFormParams(), - request.getQueryParams(), - 137131201, - Utf8UrlEncoder.percentEncodeQueryElement("ZLc92RAkooZcIO/0cctl0Q==")).toString(); - - assertEquals("POST&" - + "http%3A%2F%2Fexample.com%2Frequest" - + "&a2%3Dr%2520b%26" - + "a3%3D2%2520q%26" - + "a3%3Da%26" - + "b5%3D%253D%25253D%26" - + "c%2540%3D%26" - + "c2%3D%26" - + "oauth_consumer_key%3D9djdj82h48djs9d2%26" - + "oauth_nonce%3DZLc92RAkooZcIO%252F0cctl0Q%253D%253D%26" - + "oauth_signature_method%3DHMAC-SHA1%26" - + "oauth_timestamp%3D137131201%26" - + "oauth_token%3Dkkk9d7dh3k39sjv7%26" - + "oauth_version%3D1.0", signatureBaseString); - } - - @RepeatedIfExceptionsTest(repeats = 5) - public void testSignatureBaseStringWithProperlyEncodedUri() throws NoSuchAlgorithmException { - Request request = post("http://example.com/request?b5=%3D%253D&a3=a&c%40=&a2=r%20b") - .addFormParam("c2", "") - .addFormParam("a3", "2 q") - .build(); - - testSignatureBaseString(request); - testSignatureBaseStringWithEncodableOAuthToken(request); - } - - @RepeatedIfExceptionsTest(repeats = 5) - public void testSignatureBaseStringWithRawUri() throws NoSuchAlgorithmException { - // note: @ is legal so don't decode it into %40 because it won't be - // encoded back - // note: we don't know how to fix a = that should have been encoded as - // %3D but who would be stupid enough to do that? - Request request = post("http://example.com/request?b5=%3D%253D&a3=a&c%40=&a2=r%20b") - .addFormParam("c2", "") - .addFormParam("a3", "2 q") - .build(); - - testSignatureBaseString(request); - testSignatureBaseStringWithEncodableOAuthToken(request); - } - - @Test - public void testSignatureBaseStringWithNoValueQueryParameter() throws NoSuchAlgorithmException { - // Query parameter with no value in OAuth1 should be treated the same as query parameter with an empty value. - // i.e."http://example.com/request?b5" == "http://example.com/request?b5=" - // Tested with http://lti.tools/oauth/ - Request request = post("http://example.com/request?b5=%3D%253D&a3=a&c%40&a2=r%20b") - .addFormParam("c2", "") - .addFormParam("a3", "2 q") - .build(); - - testSignatureBaseString(request); - testSignatureBaseStringWithEncodableOAuthToken(request); - } - - @Test - public void testDuplicatingNullValueFormParameter() throws Exception { - // Form parameter with no value in OAuth1 should be treated the same as form parameter with an empty value. - // Tested with http://lti.tools/oauth/ - Request request = post("http://example.com/request?b5=%3D%253D&a3=a&c%40=&a2=r%20b") - .addFormParam("c2", "") - .addFormParam("a3", "2 q") - .addFormParam("c2", null) - .build(); - - String signatureBaseString = new OAuthSignatureCalculatorInstance() - .signatureBaseString(// - new ConsumerKey(CONSUMER_KEY, CONSUMER_SECRET), - new RequestToken(TOKEN_KEY, TOKEN_SECRET), - request.getUri(), - request.getMethod(), - request.getFormParams(), - request.getQueryParams(), - TIMESTAMP, - NONCE).toString(); - assertEquals("POST&" + - "http%3A%2F%2Fexample.com%2Frequest" + - "&a2%3Dr%2520b%26" + - "a3%3D2%2520q%26" + - "a3%3Da%26" + - "b5%3D%253D%25253D%26" + - "c%2540%3D%26" + - "c2%3D%26" + - "c2%3D%26" + - "oauth_consumer_key%3Ddpf43f3p2l4k3l03%26" + - "oauth_nonce%3Dkllo9940pd9333jh%26" + - "oauth_signature_method%3DHMAC-SHA1%26" + - "oauth_timestamp%3D1191242096%26" + - "oauth_token%3Dnnch734d00sl2jdk%26" + - "oauth_version%3D1.0", signatureBaseString); - } - - // based on the reference test case from - // http://oauth.pbwiki.com/TestCases - @RepeatedIfExceptionsTest(repeats = 5) - public void testGetCalculateSignature() throws Exception { - Request request = get("http://photos.example.net/photos") - .addQueryParam("file", "vacation.jpg") - .addQueryParam("size", "original") - .build(); - - String signature = new OAuthSignatureCalculatorInstance() - .computeSignature(new ConsumerKey(CONSUMER_KEY, CONSUMER_SECRET), - new RequestToken(TOKEN_KEY, TOKEN_SECRET), - request.getUri(), - request.getMethod(), - request.getFormParams(), - request.getQueryParams(), - TIMESTAMP, - NONCE); - - assertEquals("tR3+Ty81lMeYAr/Fid0kMTYa/WM=", signature); - } - - @RepeatedIfExceptionsTest(repeats = 5) - public void testPostCalculateSignature() throws UnsupportedEncodingException { - StaticOAuthSignatureCalculator calc = // - new StaticOAuthSignatureCalculator(// - new ConsumerKey(CONSUMER_KEY, CONSUMER_SECRET), - new RequestToken(TOKEN_KEY, TOKEN_SECRET), - NONCE, - TIMESTAMP); - - final Request req = post("http://photos.example.net/photos") - .addFormParam("file", "vacation.jpg") - .addFormParam("size", "original") - .setSignatureCalculator(calc) - .build(); - - // From the signature tester, POST should look like: - // normalized parameters: - // file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original - // signature base string: - // POST&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal - // signature: wPkvxykrw+BTdCcGqKr+3I+PsiM= - // header: OAuth - // realm="",oauth_version="1.0",oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_signature_method="HMAC-SHA1",oauth_signature="wPkvxykrw%2BBTdCcGqKr%2B3I%2BPsiM%3D" - - String authHeader = req.getHeaders().get(AUTHORIZATION); - Matcher m = Pattern.compile("oauth_signature=\"(.+?)\"").matcher(authHeader); - assertTrue(m.find()); - String encodedSig = m.group(1); - String sig = URLDecoder.decode(encodedSig, StandardCharsets.UTF_8); - - assertEquals("wPkvxykrw+BTdCcGqKr+3I+PsiM=", sig); - } - - @RepeatedIfExceptionsTest(repeats = 5) - public void testGetWithRequestBuilder() throws UnsupportedEncodingException { - StaticOAuthSignatureCalculator calc = - new StaticOAuthSignatureCalculator( - new ConsumerKey(CONSUMER_KEY, CONSUMER_SECRET), - new RequestToken(TOKEN_KEY, TOKEN_SECRET), - NONCE, - TIMESTAMP); - - final Request req = get("http://photos.example.net/photos") - .addQueryParam("file", "vacation.jpg") - .addQueryParam("size", "original") - .setSignatureCalculator(calc) - .build(); - - final List params = req.getQueryParams(); - assertEquals(2, params.size()); - - // From the signature tester, the URL should look like: - // normalized parameters: - // file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original - // signature base string: - // GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal - // signature: tR3+Ty81lMeYAr/Fid0kMTYa/WM= - // Authorization header: OAuth - // realm="",oauth_version="1.0",oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_signature_method="HMAC-SHA1",oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D" - - String authHeader = req.getHeaders().get(AUTHORIZATION); - Matcher m = Pattern.compile("oauth_signature=\"(.+?)\"").matcher(authHeader); - assertTrue(m.find()); - String encodedSig = m.group(1); - String sig = URLDecoder.decode(encodedSig, StandardCharsets.UTF_8); - - assertEquals(sig, "tR3+Ty81lMeYAr/Fid0kMTYa/WM="); - assertEquals(req.getUrl(), "http://photos.example.net/photos?file=vacation.jpg&size=original"); - } - - @RepeatedIfExceptionsTest(repeats = 5) - public void testGetWithRequestBuilderAndQuery() throws UnsupportedEncodingException { - StaticOAuthSignatureCalculator calc = // - new StaticOAuthSignatureCalculator(// - new ConsumerKey(CONSUMER_KEY, CONSUMER_SECRET), - new RequestToken(TOKEN_KEY, TOKEN_SECRET), - NONCE, - TIMESTAMP); - - final Request req = get("http://photos.example.net/photos?file=vacation.jpg&size=original") - .setSignatureCalculator(calc) - .build(); - - final List params = req.getQueryParams(); - assertEquals(params.size(), 2); - - // From the signature tester, the URL should look like: - // normalized parameters: - // file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original - // signature base string: - // GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal - // signature: tR3+Ty81lMeYAr/Fid0kMTYa/WM= - // Authorization header: OAuth - // realm="",oauth_version="1.0",oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_signature_method="HMAC-SHA1",oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D" - - String authHeader = req.getHeaders().get(AUTHORIZATION); - Matcher m = Pattern.compile("oauth_signature=\"(.+?)\"").matcher(authHeader); - assertTrue(m.find()); - String encodedSig = m.group(1); - String sig = URLDecoder.decode(encodedSig, StandardCharsets.UTF_8); - - assertEquals("tR3+Ty81lMeYAr/Fid0kMTYa/WM=", sig); - assertEquals("http://photos.example.net/photos?file=vacation.jpg&size=original", req.getUrl()); - assertEquals( - "OAuth oauth_consumer_key=\"dpf43f3p2l4k3l03\", oauth_token=\"nnch734d00sl2jdk\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D\", oauth_timestamp=\"1191242096\", oauth_nonce=\"kllo9940pd9333jh\", oauth_version=\"1.0\"", - authHeader); - } - - @RepeatedIfExceptionsTest(repeats = 5) - public void testWithNullRequestToken() throws NoSuchAlgorithmException { - final Request request = get("http://photos.example.net/photos?file=vacation.jpg&size=original").build(); - - String signatureBaseString = new OAuthSignatureCalculatorInstance() - .signatureBaseString(// - new ConsumerKey("9djdj82h48djs9d2", CONSUMER_SECRET), - new RequestToken(null, null), - request.getUri(), - request.getMethod(), - request.getFormParams(), - request.getQueryParams(), - 137131201, - Utf8UrlEncoder.percentEncodeQueryElement("ZLc92RAkooZcIO/0cctl0Q==")).toString(); - - assertEquals("GET&" + - "http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26" + - "oauth_consumer_key%3D9djdj82h48djs9d2%26" + - "oauth_nonce%3DZLc92RAkooZcIO%252F0cctl0Q%253D%253D%26" + - "oauth_signature_method%3DHMAC-SHA1%26" + - "oauth_timestamp%3D137131201%26" + - "oauth_version%3D1.0%26size%3Doriginal", signatureBaseString); - } - - @RepeatedIfExceptionsTest(repeats = 5) - public void testWithStarQueryParameterValue() throws NoSuchAlgorithmException { - final Request request = get("http://term.ie/oauth/example/request_token.php?testvalue=*").build(); - - String signatureBaseString = new OAuthSignatureCalculatorInstance() - .signatureBaseString( - new ConsumerKey("key", "secret"), - new RequestToken(null, null), - request.getUri(), - request.getMethod(), - request.getFormParams(), - request.getQueryParams(), - 1469019732, - "6ad17f97334700f3ec2df0631d5b7511").toString(); - - assertEquals("GET&" + - "http%3A%2F%2Fterm.ie%2Foauth%2Fexample%2Frequest_token.php&" - + "oauth_consumer_key%3Dkey%26" - + "oauth_nonce%3D6ad17f97334700f3ec2df0631d5b7511%26" - + "oauth_signature_method%3DHMAC-SHA1%26" - + "oauth_timestamp%3D1469019732%26" - + "oauth_version%3D1.0%26" - + "testvalue%3D%252A", signatureBaseString); - } - - @RepeatedIfExceptionsTest(repeats = 5) - public void testSignatureGenerationWithAsteriskInPath() throws Exception { - ConsumerKey consumerKey = new ConsumerKey("key", "secret"); - RequestToken requestToken = new RequestToken(null, null); - String nonce = "6ad17f97334700f3ec2df0631d5b7511"; - long timestamp = 1469019732; - - final Request request = get("http://example.com/oauth/example/*path/wi*th/asterisks*").build(); - - String expectedSignature = "cswi/v3ZqhVkTyy5MGqW841BxDA="; - String actualSignature = new OAuthSignatureCalculatorInstance().computeSignature( - consumerKey, - requestToken, - request.getUri(), - request.getMethod(), - request.getFormParams(), - request.getQueryParams(), - timestamp, - nonce); - assertEquals(expectedSignature, actualSignature); - - String generatedAuthHeader = new OAuthSignatureCalculatorInstance().computeAuthorizationHeader(consumerKey, requestToken, actualSignature, timestamp, nonce); - assertTrue(generatedAuthHeader.contains("oauth_signature=\"cswi%2Fv3ZqhVkTyy5MGqW841BxDA%3D\"")); - } - - @RepeatedIfExceptionsTest(repeats = 5) - public void testPercentEncodeKeyValues() { - // see https://github.com/AsyncHttpClient/async-http-client/issues/1415 - String keyValue = "\u3b05\u000c\u375b"; - - ConsumerKey consumer = new ConsumerKey(keyValue, "secret"); - RequestToken reqToken = new RequestToken(keyValue, "secret"); - OAuthSignatureCalculator calc = new OAuthSignatureCalculator(consumer, reqToken); - - RequestBuilder reqBuilder = new RequestBuilder() - .setUrl("https://api.dropbox.com/1/oauth/access_token?oauth_token=%EC%AD%AE%E3%AC%82%EC%BE%B8%E7%9C%9A%E8%BD%BD%E1%94%A5%E8%AD%AF%E8%98%93%E0%B9%99%E5%9E%96%EF%92%A2%EA%BC%97%EA%90%B0%E4%8A%91%E8%97%BF%EF%A8%BB%E5%B5%B1%DA%98%E2%90%87%E2%96%96%EE%B5%B5%E7%B9%AD%E9%AD%87%E3%BE%93%E5%AF%92%EE%BC%8F%E3%A0%B2%E8%A9%AB%E1%8B%97%EC%BF%80%EA%8F%AE%ED%87%B0%E5%97%B7%E9%97%BF%E8%BF%87%E6%81%A3%E5%BB%A1%EC%86%92%E8%92%81%E2%B9%94%EB%B6%86%E9%AE%8A%E6%94%B0%EE%AC%B5%E6%A0%99%EB%8B%AD%EB%BA%81%E7%89%9F%E5%B3%B7%EA%9D%B7%EC%A4%9C%E0%BC%BA%EB%BB%B9%ED%84%A9%E8%A5%B9%E8%AF%A0%E3%AC%85%0C%E3%9D%9B%E8%B9%8B%E6%BF%8C%EB%91%98%E7%8B%B3%E7%BB%A8%E2%A7%BB%E6%A3%84%E1%AB%B2%E8%8D%93%E4%BF%98%E9%B9%B9%EF%9A%8B%E8%A5%93"); - Request req = reqBuilder.build(); - - calc.calculateAndAddSignature(req, reqBuilder); - } -} diff --git a/client/src/test/java/org/asynchttpclient/oauth/StaticOAuthSignatureCalculator.java b/client/src/test/java/org/asynchttpclient/oauth/StaticOAuthSignatureCalculator.java deleted file mode 100644 index 9e3f2cfe9..000000000 --- a/client/src/test/java/org/asynchttpclient/oauth/StaticOAuthSignatureCalculator.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2017-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.oauth; - -import io.netty.handler.codec.http.HttpHeaderNames; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilderBase; -import org.asynchttpclient.SignatureCalculator; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -class StaticOAuthSignatureCalculator implements SignatureCalculator { - - private final ConsumerKey consumerKey; - private final RequestToken requestToken; - private final String nonce; - private final long timestamp; - - StaticOAuthSignatureCalculator(ConsumerKey consumerKey, RequestToken requestToken, String nonce, long timestamp) { - this.consumerKey = consumerKey; - this.requestToken = requestToken; - this.nonce = nonce; - this.timestamp = timestamp; - } - - @Override - public void calculateAndAddSignature(Request request, RequestBuilderBase requestBuilder) { - try { - String authorization = new OAuthSignatureCalculatorInstance().computeAuthorizationHeader( - consumerKey, - requestToken, - request.getUri(), - request.getMethod(), - request.getFormParams(), - request.getQueryParams(), - timestamp, - nonce); - requestBuilder.setHeader(HttpHeaderNames.AUTHORIZATION, authorization); - } catch (InvalidKeyException | NoSuchAlgorithmException e) { - throw new IllegalArgumentException(e); - } - } -} 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/HttpTest.java b/client/src/test/java/org/asynchttpclient/testserver/HttpTest.java index 44d31269f..b41b6ab1b 100644 --- a/client/src/test/java/org/asynchttpclient/testserver/HttpTest.java +++ b/client/src/test/java/org/asynchttpclient/testserver/HttpTest.java @@ -15,15 +15,18 @@ */ package org.asynchttpclient.testserver; +import io.github.nettyplus.leakdetector.junit.NettyLeakDetectorExtension; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.DefaultAsyncHttpClientConfig; +import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.asynchttpclient.Dsl.asyncHttpClient; import static org.asynchttpclient.Dsl.config; +@ExtendWith(NettyLeakDetectorExtension.class) public abstract class HttpTest { protected static final String COMPLETED_EVENT = "Completed"; 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/webdav/WebdavTest.java b/client/src/test/java/org/asynchttpclient/webdav/WebdavTest.java deleted file mode 100644 index ce351c03e..000000000 --- a/client/src/test/java/org/asynchttpclient/webdav/WebdavTest.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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.webdav; - -import jakarta.servlet.ServletConfig; -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletException; -import org.apache.catalina.Context; -import org.apache.catalina.servlets.WebdavServlet; -import org.apache.catalina.startup.Tomcat; -import org.asynchttpclient.AsyncHttpClient; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; - -import java.io.File; -import java.util.Enumeration; - -import static org.asynchttpclient.Dsl.asyncHttpClient; -import static org.asynchttpclient.Dsl.delete; - -public class WebdavTest { - - private Tomcat tomcat; - private int port1; - - @SuppressWarnings("serial") - @BeforeEach - public void setUpGlobal() throws Exception { - String path = new File(".").getAbsolutePath() + "/target"; - - tomcat = new Tomcat(); - tomcat.setHostname("localhost"); - tomcat.setPort(0); - tomcat.setBaseDir(path); - Context ctx = tomcat.addContext("", path); - - Tomcat.addServlet(ctx, "webdav", new WebdavServlet() { - @Override - public void init(ServletConfig config) throws ServletException { - - super.init(new ServletConfig() { - - @Override - public String getServletName() { - return config.getServletName(); - } - - @Override - public ServletContext getServletContext() { - return config.getServletContext(); - } - - @Override - public Enumeration getInitParameterNames() { - // FIXME - return config.getInitParameterNames(); - } - - @Override - public String getInitParameter(String name) { - switch (name) { - case "readonly": - return "false"; - case "listings": - return "true"; - default: - return config.getInitParameter(name); - } - } - }); - } - - }); - ctx.addServletMappingDecoded("/*", "webdav"); - tomcat.start(); - port1 = tomcat.getConnector().getLocalPort(); - } - - @AfterEach - public void tearDownGlobal() throws Exception { - tomcat.stop(); - } - - private String getTargetUrl() { - return String.format("http://localhost:%s/folder1", port1); - } - - @AfterEach - public void clean() throws Exception { - try (AsyncHttpClient client = asyncHttpClient()) { - client.executeRequest(delete(getTargetUrl())).get(); - } - } - -// @RepeatedIfExceptionsTest(repeats = 5) -// public void mkcolWebDavTest1() throws Exception { -// try (AsyncHttpClient client = asyncHttpClient()) { -// Request mkcolRequest = new RequestBuilder("MKCOL").setUrl(getTargetUrl()).build(); -// Response response = client.executeRequest(mkcolRequest).get(); -// assertEquals(201, response.getStatusCode()); -// } -// } -// -// @RepeatedIfExceptionsTest(repeats = 5) -// public void mkcolWebDavTest2() throws Exception { -// try (AsyncHttpClient client = asyncHttpClient()) { -// Request mkcolRequest = new RequestBuilder("MKCOL").setUrl(getTargetUrl() + "/folder2").build(); -// Response response = client.executeRequest(mkcolRequest).get(); -// assertEquals(409, response.getStatusCode()); -// } -// } -// -// @RepeatedIfExceptionsTest(repeats = 5) -// public void basicPropFindWebDavTest() throws Exception { -// try (AsyncHttpClient client = asyncHttpClient()) { -// Request propFindRequest = new RequestBuilder("PROPFIND").setUrl(getTargetUrl()).build(); -// Response response = client.executeRequest(propFindRequest).get(); -// -// assertEquals(404, response.getStatusCode()); -// } -// } -// -// @RepeatedIfExceptionsTest(repeats = 5) -// public void propFindWebDavTest() throws Exception { -// try (AsyncHttpClient client = asyncHttpClient()) { -// Request mkcolRequest = new RequestBuilder("MKCOL").setUrl(getTargetUrl()).build(); -// Response response = client.executeRequest(mkcolRequest).get(); -// assertEquals(201, response.getStatusCode()); -// -// Request putRequest = put(getTargetUrl() + "/Test.txt").setBody("this is a test").build(); -// response = client.executeRequest(putRequest).get(); -// assertEquals(201, response.getStatusCode()); -// -// Request propFindRequest = new RequestBuilder("PROPFIND").setUrl(getTargetUrl() + "/Test.txt").build(); -// response = client.executeRequest(propFindRequest).get(); -// -// assertEquals(207, response.getStatusCode()); -// String body = response.getResponseBody(); -// assertTrue(body.contains("HTTP/1.1 200"), "Got " + body); -// } -// } -// -// @RepeatedIfExceptionsTest(repeats = 5) -// public void propFindCompletionHandlerWebDavTest() throws Exception { -// try (AsyncHttpClient c = asyncHttpClient()) { -// Request mkcolRequest = new RequestBuilder("MKCOL").setUrl(getTargetUrl()).build(); -// Response response = c.executeRequest(mkcolRequest).get(); -// assertEquals(201, response.getStatusCode()); -// -// Request propFindRequest = new RequestBuilder("PROPFIND").setUrl(getTargetUrl()).build(); -// WebDavResponse webDavResponse = c.executeRequest(propFindRequest, new WebDavCompletionHandlerBase() { -// -// @Override -// public void onThrowable(Throwable t) { -// t.printStackTrace(); -// } -// -// @Override -// public WebDavResponse onCompleted(WebDavResponse response) { -// return response; -// } -// }).get(); -// -// assertEquals(207, webDavResponse.getStatusCode()); -// String body = webDavResponse.getResponseBody(); -// assertTrue(body.contains("HTTP/1.1 200"), "Got " + body); -// } -// } -} 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/mvnw b/mvnw old mode 100644 new mode 100755 index b7f064624..5643201c7 --- a/mvnw +++ b/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.1.1 +# Maven Start Up Batch script # # Required ENV vars: # ------------------ @@ -27,6 +27,7 @@ # # Optional ENV vars # ----------------- +# M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -61,9 +62,9 @@ case "`uname`" in # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then - JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME + export JAVA_HOME="`/usr/libexec/java_home`" else - JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + export JAVA_HOME="/Library/Java/Home" fi fi ;; @@ -75,8 +76,36 @@ if [ -z "$JAVA_HOME" ] ; then fi fi +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` [ -n "$CLASSPATH" ] && @@ -85,6 +114,8 @@ fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" fi @@ -132,9 +163,12 @@ if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { + if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" @@ -154,7 +188,7 @@ find_maven_basedir() { fi # end of workaround done - printf '%s' "$(cd "$basedir"; pwd)" + echo "${basedir}" } # concatenates all lines of a file @@ -164,16 +198,11 @@ concat_lines() { fi } -BASE_DIR=$(find_maven_basedir "$(dirname $0)") +BASE_DIR=`find_maven_basedir "$(pwd)"` if [ -z "$BASE_DIR" ]; then exit 1; fi -MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi - ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. @@ -187,16 +216,16 @@ else echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." fi if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" fi while IFS="=" read key value; do - case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; esac done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $wrapperUrl" + echo "Downloading from: $jarUrl" fi wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" if $cygwin; then @@ -204,49 +233,42 @@ else fi if command -v wget > /dev/null; then - QUIET="--quiet" if [ "$MVNW_VERBOSE" = true ]; then echo "Found wget ... using wget" - QUIET="" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi - [ $? -eq 0 ] || rm -f "$wrapperJarPath" elif command -v curl > /dev/null; then - QUIET="--silent" if [ "$MVNW_VERBOSE" = true ]; then echo "Found curl ... using curl" - QUIET="" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L + curl -o "$wrapperJarPath" "$jarUrl" -f else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f fi - [ $? -eq 0 ] || rm -f "$wrapperJarPath" + else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" fi - javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then - javaSource=`cygpath --path --windows "$javaSource"` javaClass=`cygpath --path --windows "$javaClass"` fi - if [ -e "$javaSource" ]; then - if [ ! -e "$javaClass" ]; then + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo " - Compiling MavenWrapperDownloader.java ..." fi # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaSource") + ("$JAVA_HOME/bin/javac" "$javaClass") fi - if [ -e "$javaClass" ]; then + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then # Running the downloader if [ "$MVNW_VERBOSE" = true ]; then echo " - Running MavenWrapperDownloader.java ..." @@ -260,10 +282,16 @@ fi # End of extension ########################################################################################## +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` [ -n "$CLASSPATH" ] && @@ -283,5 +311,6 @@ exec "$JAVACMD" \ $MAVEN_OPTS \ $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 474c9d6b7..23b7079a3 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,187 +1,188 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.1.1 -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* -if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" - -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %WRAPPER_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% ^ - %JVM_CONFIG_MAVEN_PROPS% ^ - %MAVEN_OPTS% ^ - %MAVEN_DEBUG_OPTS% ^ - -classpath %WRAPPER_JAR% ^ - "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ - %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" -if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%"=="on" pause - -if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% - -cmd /C exit /B %ERROR_CODE% +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 42c96fc06..e55fe8a26 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ @@ -257,7 +337,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + 3.5.2 @{argLine} --add-exports java.base/jdk.internal.misc=ALL-UNNAMED @@ -268,7 +348,7 @@ org.jacoco jacoco-maven-plugin - 0.8.9 + 0.8.12 @@ -291,7 +371,6 @@ 3.2.1 - attach-sources jar-no-fork @@ -302,7 +381,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.2.0 + 3.11.1 attach-javadocs @@ -316,7 +395,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.8 + 1.7.0 true ossrh @@ -329,7 +408,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.2.7 sign-artifacts @@ -343,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 + + +