diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml new file mode 100644 index 0000000000..2586cf3c6f --- /dev/null +++ b/.github/workflows/builds.yml @@ -0,0 +1,59 @@ +name: Build Check + +on: + schedule: + - cron: '0 12 * * *' + +jobs: + Verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Grant Permission + run: chmod +x ./mvnw + - uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: '11' + - name: Verify + run: ./mvnw -B -ntp clean verify -DskipTests -Dgpg.skip=true + + RunOnLinux: + runs-on: ubuntu-latest + needs: Verify + steps: + - uses: actions/checkout@v4 + - name: Grant Permission + run: chmod +x ./mvnw + - uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: '11' + - name: Run Tests + run: ./mvnw -B -ntp test + + RunOnMacOs: + runs-on: macos-latest + needs: Verify + steps: + - uses: actions/checkout@v4 + - name: Grant Permission + run: chmod +x ./mvnw + - uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: '11' + - name: Run Tests + run: ./mvnw -B -ntp test + + RunOnWindows: + runs-on: windows-latest + needs: Verify + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: '11' + - name: Run Tests + run: ./mvnw.cmd -B -ntp test diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000000..1f49d31122 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,55 @@ +# This workflow is designed to build PRs for AHC. Note that it does not actually publish AHC, just builds and test it. +# Docs: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Build PR + +on: + push: + branches: + - main + pull_request: + + workflow_dispatch: + inputs: + name: + description: 'Github Actions' + required: true + default: 'Github Actions' + +jobs: + RunOnLinux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Grant Permission + run: sudo chmod +x ./mvnw + - uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: '11' + - name: Run Tests + run: ./mvnw -B -ntp clean test + + RunOnMacOs: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Grant Permission + run: sudo chmod +x ./mvnw + - uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: '11' + - name: Run Tests + run: ./mvnw -B -ntp clean test + + RunOnWindows: + runs-on: windows-latest + steps: + - 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..b175fa865c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: Release + +on: + workflow_dispatch: + inputs: + name: + description: 'Github Actions - Release' + required: true + default: 'Github Actions - Release' + +jobs: + + Publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Grant Permission + run: sudo chmod +x ./mvnw + + - uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: '11' + + - name: Remove old Maven Settings + run: rm -f /home/runner/.m2/settings.xml + + - name: Maven Settings + uses: s4u/maven-settings-action@v3.1.0 + with: + servers: | + [{ + "id": "ossrh", + "username": "${{ secrets.OSSRH_USERNAME }}", + "password": "${{ secrets.OSSRH_PASSWORD }}" + }] + + - name: Import GPG + uses: crazy-max/ghaction-import-gpg@v6.3.0 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + + - name: Build + run: mvn -ntp -B clean verify install -DskipTests + + - name: Publish to Maven Central + env: + GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + run: mvn -ntp -B deploy -DskipTests -Dgpg.keyname=${GPG_KEY_NAME} -Dgpg.passphrase=${GPG_PASSPHRASE} diff --git a/.gitignore b/.gitignore index b023787595..d424b2597a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ test-output MANIFEST.MF work atlassian-ide-plugin.xml +/bom/.flattened-pom.xml diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 0000000000..32599cefea --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1,10 @@ +--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000..c1dd12f176 Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..4a95a1367b --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# 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 +# +# 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://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/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000000..d548766a4e --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,29 @@ +## From 2.2 to 2.3 + +* New `isFilterInsecureCipherSuites` config to disable unsecure and weak ciphers filtering performed internally in Netty. + +## From 2.1 to 2.2 + +* New [Typesafe config](https://github.com/lightbend/config) extra module +* new `enableWebSocketCompression` config to enable per-message and per-frame WebSocket compression extension + +## From 2.0 to 2.1 + +* AHC 2.1 targets Netty 4.1. +* `org.asynchttpclient.HttpResponseHeaders` was [dropped](https://github.com/AsyncHttpClient/async-http-client/commit/f4786f3ac7699f8f8664e7c7db0b7097585a0786) in favor + of `io.netty.handler.codec.http.HttpHeaders`. +* `org.asynchttpclient.cookie.Cookie` was [dropped](https://github.com/AsyncHttpClient/async-http-client/commit/a6d659ea0cc11fa5131304d8a04a7ba89c7a66af) in favor + of `io.netty.handler.codec.http.cookie.Cookie` as AHC's cookie parsers were contributed to Netty. +* AHC now has a RFC6265 `CookieStore` that is enabled by default. Implementation can be changed in `AsyncHttpClientConfig`. +* `AsyncHttpClient` now exposes stats with `getClientStats`. +* `AsyncHandlerExtensions` was [dropped](https://github.com/AsyncHttpClient/async-http-client/commit/1972c9b9984d6d9f9faca6edd4f2159013205aea) in favor of default methods + in `AsyncHandler`. +* `WebSocket` and `WebSocketListener` methods were renamed to mention frames +* `AsyncHttpClientConfig` various changes: + * new `getCookieStore` now lets you configure a CookieStore (enabled by default) + * new `isAggregateWebSocketFrameFragments` now lets you disable WebSocket fragmented frames aggregation + * new `isUseLaxCookieEncoder` lets you loosen cookie chars validation + * `isAcceptAnyCertificate` was dropped, as it didn't do what its name stated + * new `isUseInsecureTrustManager` lets you use a permissive TrustManager, that would typically let you accept self-signed certificates + * new `isDisableHttpsEndpointIdentificationAlgorithm` disables setting `HTTPS` algorithm on the SSLEngines, typically disables SNI and HTTPS hostname verification + * new `isAggregateWebSocketFrameFragments` lets you disable fragmented WebSocket frames aggregation diff --git a/LICENSE-2.0.txt b/LICENSE-2.0.txt deleted file mode 100644 index d645695673..0000000000 --- a/LICENSE-2.0.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000000..85a16d3d06 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,13 @@ + 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. + 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. diff --git a/LICENSES/LICENSE.zstd-jni.txt b/LICENSES/LICENSE.zstd-jni.txt new file mode 100644 index 0000000000..66abb8ae78 --- /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 2bb2b19ad0..0272134ed1 100644 --- a/README.md +++ b/README.md @@ -1,207 +1,263 @@ -Async Http Client ------------------ +# Async Http Client +[![Build](https://github.com/AsyncHttpClient/async-http-client/actions/workflows/builds.yml/badge.svg)](https://github.com/AsyncHttpClient/async-http-client/actions/workflows/builds.yml) +![Maven Central](https://img.shields.io/maven-central/v/org.asynchttpclient/async-http-client) -Getting started [HTML](http://sonatype.github.com/async-http-client/) [PDF](http://is.gd/kexrN) - With [WebSockets](http://jfarcand.wordpress.com/2011/12/21/writing-websocket-clients-using-asynchttpclient/) +Follow [@AsyncHttpClient](https://twitter.com/AsyncHttpClient) on Twitter. -Async Http Client library purpose is to allow Java applications to easily execute HTTP requests and asynchronously process the HTTP responses. The library also supports the WebSocket Protocol. The Async HTTP Client library is simple to use. First, in order to add it to your Maven project, simply add this dependency: +The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. +The library also supports the WebSocket Protocol. +It's built on top of [Netty](https://github.com/netty/netty). It's compiled with Java 11. + +## Installation + +Binaries are deployed on Maven Central. +Add a dependency on the main AsyncHttpClient artifact: + +Maven: ```xml - - com.ning - async-http-client - 1.8.13 - + + + org.asynchttpclient + async-http-client + 3.0.2 + + ``` -You can also download the artifact +Gradle: +```groovy +dependencies { + implementation 'org.asynchttpclient:async-http-client:3.0.2' +} +``` -[Maven Search](http://search.maven.org) +### Dsl -Then in your code you can simply do [Javadoc](http://asynchttpclient.github.io/async-http-client/apidocs/reference/packages.html) +Import the Dsl helpers to use convenient methods to bootstrap components: ```java -import com.ning.http.client.*; -import java.util.concurrent.Future; - -AsyncHttpClient asyncHttpClient = new AsyncHttpClient(); -Future f = asyncHttpClient.prepareGet("http://www.ning.com/").execute(); -Response r = f.get(); +import static org.asynchttpclient.Dsl.*; ``` -Note that in this case all the content must be read fully in memory, even if you used `getResponseBodyAsStream()` method on returned `Response` object. - -You can also accomplish asynchronous (non-blocking) operation without using a Future if you want to receive and process the response in your handler: +### Client ```java -import com.ning.http.client.*; -import java.util.concurrent.Future; - -AsyncHttpClient asyncHttpClient = new AsyncHttpClient(); -asyncHttpClient.prepareGet("http://www.ning.com/").execute(new AsyncCompletionHandler(){ - - @Override - public Response onCompleted(Response response) throws Exception{ - // Do something with the Response - // ... - return response; - } - - @Override - public void onThrowable(Throwable t){ - // Something wrong happened. - } -}); +import static org.asynchttpclient.Dsl.*; + +AsyncHttpClient asyncHttpClient=asyncHttpClient(); ``` -(this will also fully read `Response` in memory before calling `onCompleted`) +AsyncHttpClient instances must be closed (call the `close` method) once you're done with them, typically when shutting down your application. +If you don't, you'll experience threads hanging and resource leaks. + +AsyncHttpClient instances are intended to be global resources that share the same lifecycle as the application. +Typically, AHC will usually underperform if you create a new client for each request, as it will create new threads and connection pools for each. +It's possible to create shared resources (EventLoop and Timer) beforehand and pass them to multiple client instances in the config. You'll then be responsible for closing +those shared resources. -You can also mix Future with AsyncHandler to only retrieve part of the asynchronous response +## Configuration + +Finally, you can also configure the AsyncHttpClient instance via its AsyncHttpClientConfig object: ```java -import com.ning.http.client.*; -import java.util.concurrent.Future; - -AsyncHttpClient asyncHttpClient = new AsyncHttpClient(); -Future f = asyncHttpClient.prepareGet("http://www.ning.com/").execute( - new AsyncCompletionHandler(){ - - @Override - public Integer onCompleted(Response response) throws Exception{ - // Do something with the Response - return response.getStatusCode(); - } - - @Override - public void onThrowable(Throwable t){ - // Something wrong happened. - } -}); - -int statusCode = f.get(); +import static org.asynchttpclient.Dsl.*; + +AsyncHttpClient c=asyncHttpClient(config().setProxyServer(proxyServer("127.0.0.1",38080))); ``` -which is something you want to do for large responses: this way you can process content as soon as it becomes available, piece by piece, without having to buffer it all in memory. +## HTTP - You have full control on the Response life cycle, so you can decide at any moment to stop processing what the server is sending back: +### Sending Requests + +### Basics + +AHC provides 2 APIs for defining requests: bound and unbound. +`AsyncHttpClient` and Dsl` provide methods for standard HTTP methods (POST, PUT, etc) but you can also pass a custom one. ```java -import com.ning.http.client.*; -import java.util.concurrent.Future; - -AsyncHttpClient c = new AsyncHttpClient(); -Future f = c.prepareGet("http://www.ning.com/").execute(new AsyncHandler() { - private ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - - @Override - public STATE onStatusReceived(HttpResponseStatus status) throws Exception { - int statusCode = status.getStatusCode(); - // The Status have been read - // If you don't want to read the headers,body or stop processing the response - if (statusCode >= 500) { - return STATE.ABORT; - } - } - - @Override - public STATE onHeadersReceived(HttpResponseHeaders h) throws Exception { - Headers headers = h.getHeaders(); - // The headers have been read - // If you don't want to read the body, or stop processing the response - return STATE.ABORT; - } - - @Override - public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - bytes.write(bodyPart.getBodyPartBytes()); - return STATE.CONTINUE; - } - - @Override - public String onCompleted() throws Exception { - // Will be invoked once the response has been fully read or a ResponseComplete exception - // has been thrown. - // NOTE: should probably use Content-Encoding from headers - return bytes.toString("UTF-8"); - } - - @Override - public void onThrowable(Throwable t) { - } -}); - -String bodyResponse = f.get(); +import org.asynchttpclient.*; + +// bound +Future whenResponse=asyncHttpClient.prepareGet("http://www.example.com/").execute(); + +// unbound + Request request=get("http://www.example.com/").build(); + Future whenResponse=asyncHttpClient.executeRequest(request); ``` -## Configuration +#### Setting Request Body + +Use the `setBody` method to add a body to the request. + +This body can be of type: + +* `java.io.File` +* `byte[]` +* `List` +* `String` +* `java.nio.ByteBuffer` +* `java.io.InputStream` +* `Publisher` +* `org.asynchttpclient.request.body.generator.BodyGenerator` + +`BodyGenerator` is a generic abstraction that let you create request bodies on the fly. +Have a look at `FeedableBodyGenerator` if you're looking for a way to pass requests chunks on the fly. + +#### Multipart + +Use the `addBodyPart` method to add a multipart part to the request. -Finally, you can also configure the AsyncHttpClient via its AsyncHttpClientConfig object: +This part can be of type: + +* `ByteArrayPart` +* `FilePart` +* `InputStreamPart` +* `StringPart` + +### Dealing with Responses + +#### Blocking on the Future + +`execute` methods return a `java.util.concurrent.Future`. You can simply block the calling thread to get the response. ```java -AsyncHttpClientConfig cf = new AsyncHttpClientConfig.Builder() - S.setProxyServer(new ProxyServer("127.0.0.1", 38080)).build(); -AsyncHttpClient c = new AsyncHttpClient(cf); +Future whenResponse=asyncHttpClient.prepareGet("http://www.example.com/").execute(); + Response response=whenResponse.get(); ``` -## WebSocket +This is useful for debugging but you'll most likely hurt performance or create bugs when running such code on production. +The point of using a non blocking client is to *NOT BLOCK* the calling thread! + +### Setting callbacks on the ListenableFuture -Async Http Client also support WebSocket by simply doing: +`execute` methods actually return a `org.asynchttpclient.ListenableFuture` similar to Guava's. +You can configure listeners to be notified of the Future's completion. ```java -WebSocket websocket = c.prepareGet(getTargetUrl()) - .execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener( - new WebSocketTextListener() { - - @Override - public void onMessage(String message) { - } - - @Override - public void onOpen(WebSocket websocket) { - websocket.sendTextMessage("...").sendBinaryMessage("..."); - } - - @Override - public void onClose(.WebSocket websocket) { - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - } - }).build()).get(); + ListenableFuture whenResponse = ???; + Runnable callback = () - > { + try { + Response response = whenResponse.get(); + System.out.println(response); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + }; + + java.util.concurrent.Executor executor = ???; + whenResponse.addListener(() - > ??? , executor); ``` -The library uses Java non blocking I/O for supporting asynchronous operations. The default asynchronous provider is build on top of [Netty](http://www.jboss.org/netty), but the library exposes a configurable provider SPI which allows to easily plug in other frameworks like [Grizzly](http://grizzly.java.net) +If the `executor` parameter is null, callback will be executed in the IO thread. +You *MUST NEVER PERFORM BLOCKING* operations in there, typically sending another request and block on a future. + +#### Using custom AsyncHandlers + +`execute` methods can take an `org.asynchttpclient.AsyncHandler` to be notified on the different events, such as receiving the status, the headers and body chunks. +When you don't specify one, AHC will use a `org.asynchttpclient.AsyncCompletionHandler`; + +`AsyncHandler` methods can let you abort processing early (return `AsyncHandler.State.ABORT`) and can let you return a computation result from `onCompleted` that will be used +as the Future's result. +See `AsyncCompletionHandler` implementation as an example. + +The below sample just capture the response status and skips processing the response body chunks. + +Note that returning `ABORT` closes the underlying connection. ```java -AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().build(); -AsyncHttpClient client = new AsyncHttpClient(new GrizzlyAsyncHttpProvider(config), config); +import static org.asynchttpclient.Dsl.*; + +import org.asynchttpclient.*; +import io.netty.handler.codec.http.HttpHeaders; + +Future whenStatusCode = asyncHttpClient.prepareGet("http://www.example.com/") + .execute(new AsyncHandler () { + private Integer status; + + @Override + public State onStatusReceived(HttpResponseStatus responseStatus) throws Exception { + status = responseStatus.getStatusCode(); + return State.ABORT; + } + + @Override + public State onHeadersReceived(HttpHeaders headers) throws Exception { + return State.ABORT; + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { + return State.ABORT; + } + + @Override + public Integer onCompleted() throws Exception{ + return status; + } + + @Override + public void onThrowable(Throwable t) { + t.printStackTrace(); + } + }); + + Integer statusCode = whenStatusCode.get(); ``` -## User Group +#### Using Continuations -Keep up to date on the library development by joining the Asynchronous HTTP Client discussion group +`ListenableFuture` has a `toCompletableFuture` method that returns a `CompletableFuture`. +Beware that canceling this `CompletableFuture` won't properly cancel the ongoing request. +There's a very good chance we'll return a `CompletionStage` instead in the next release. + +```java +CompletableFuture whenResponse=asyncHttpClient + .prepareGet("http://www.example.com/") + .execute() + .toCompletableFuture() + .exceptionally(t->{ /* Something wrong happened... */ }) + .thenApply(response->{ /* Do something with the Response */ return resp;}); + whenResponse.join(); // wait for completion +``` -[Google Group](http://groups.google.com/group/asynchttpclient) +You may get the complete maven project for this simple demo +from [org.asynchttpclient.example](https://github.com/AsyncHttpClient/async-http-client/tree/master/example/src/main/java/org/asynchttpclient/example) -or follow us on [Twitter](http://twitter.com/jfarcand) +## WebSocket -[![githalytics.com alpha](https://cruel-carlota.pagodabox.com/6433679063b2351599c6ca44a08246a2 "githalytics.com")](http://githalytics.com/AsyncHttpClient/async-http-client) +Async Http Client also supports WebSocket. +You need to pass a `WebSocketUpgradeHandler` where you would register a `WebSocketListener`. -## Contributing +```java +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(); +``` -Of course, Pull Requests are welcome. +## User Group -Here a the few rules we'd like you to respect if you do so: +Keep up to date on the library development by joining the Asynchronous HTTP Client discussion group -* Only edit the code related to the suggested change, so DON'T automatically format the classes you've edited. -* Respect the formatting rules: - * Ident with 4 spaces - * Use a 140 chars line max length - * Don't use * imports - * Stick to the org, com, javax, java imports order -* Your PR can contain multiple commits when submitting, but once it's been reviewed, we'll ask you to squash them into a single one -* Regarding licensing: - * You must be the original author of the code you suggest. - * If not, you have to prove that the original code was published under Apache License 2 and properly mention original copyrights. +[GitHub Discussions](https://github.com/AsyncHttpClient/async-http-client/discussions) diff --git a/api/pom.xml b/api/pom.xml deleted file mode 100644 index bf1589ebd2..0000000000 --- a/api/pom.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - org.asynchttpclient - async-http-client-project - 2.0.0-SNAPSHOT - - 4.0.0 - async-http-client-api - Asynchronous Http Client API - - The Async Http Client (AHC) API classes. - - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.2 - - - - test-jar - - - - - - - - - - org.slf4j - slf4j-api - 1.7.6 - - - - \ No newline at end of file diff --git a/api/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java b/api/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java deleted file mode 100644 index b27a99dfb4..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * An {@link AsyncHandler} augmented with an {@link #onCompleted(Response)} convenience method which gets called - * when the {@link Response} processing is finished. This class also implement the {@link ProgressAsyncHandler} callback, - * all doing nothing except returning {@link org.asynchttpclient.AsyncHandler.STATE#CONTINUE} - * - * @param Type of the value that will be returned by the associated {@link java.util.concurrent.Future} - */ -public abstract class AsyncCompletionHandler implements AsyncHandler, ProgressAsyncHandler { - - private final Logger log = LoggerFactory.getLogger(AsyncCompletionHandlerBase.class); - private final Response.ResponseBuilder builder = new Response.ResponseBuilder(); - - /** - * {@inheritDoc} - */ - public STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { - builder.accumulate(content); - return STATE.CONTINUE; - } - - /** - * {@inheritDoc} - */ - public STATE onStatusReceived(final HttpResponseStatus status) throws Exception { - builder.reset(); - builder.accumulate(status); - return STATE.CONTINUE; - } - - /** - * {@inheritDoc} - */ - public STATE onHeadersReceived(final HttpResponseHeaders headers) throws Exception { - builder.accumulate(headers); - return STATE.CONTINUE; - } - - /** - * {@inheritDoc} - */ - public final T onCompleted() throws Exception { - return onCompleted(builder.build()); - } - - /** - * {@inheritDoc} - */ - public void onThrowable(Throwable t) { - log.debug(t.getMessage(), t); - } - - /** - * Invoked once the HTTP response processing is finished. - *

- *

- * Gets always invoked as last callback method. - * - * @param response The {@link Response} - * @return T Value that will be returned by the associated {@link java.util.concurrent.Future} - * @throws Exception if something wrong happens - */ - abstract public T onCompleted(Response response) throws Exception; - - /** - * Invoked when the content (a {@link java.io.File}, {@link String} or {@link java.io.FileInputStream} has been fully - * written on the I/O socket. - * - * @return a {@link org.asynchttpclient.AsyncHandler.STATE} telling to CONTINUE or ABORT the current processing. - */ - public STATE onHeaderWriteCompleted() { - return STATE.CONTINUE; - } - - /** - * Invoked when the content (a {@link java.io.File}, {@link String} or {@link java.io.FileInputStream} has been fully - * written on the I/O socket. - * - * @return a {@link org.asynchttpclient.AsyncHandler.STATE} telling to CONTINUE or ABORT the current processing. - */ - public STATE onContentWriteCompleted() { - return STATE.CONTINUE; - } - - /** - * Invoked when the I/O operation associated with the {@link Request} body as been progressed. - * - * @param amount The amount of bytes to transfer - * @param current The amount of bytes transferred - * @param total The total number of bytes transferred - * @return a {@link org.asynchttpclient.AsyncHandler.STATE} telling to CONTINUE or ABORT the current processing. - */ - public STATE onContentWriteProgress(long amount, long current, long total) { - return STATE.CONTINUE; - } -} diff --git a/api/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java b/api/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java deleted file mode 100644 index cd5f0a6087..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Simple {@link AsyncHandler} of type {@link Response} - */ -public class AsyncCompletionHandlerBase extends AsyncCompletionHandler { - private final Logger log = LoggerFactory.getLogger(AsyncCompletionHandlerBase.class); - - /** - * {@inheritDoc} - */ - @Override - public Response onCompleted(Response response) throws Exception { - return response; - } - - /** - * {@inheritDoc} - */ - @Override - public void onThrowable(Throwable t) { - log.debug(t.getMessage(), t); - } -} diff --git a/api/src/main/java/org/asynchttpclient/AsyncHandler.java b/api/src/main/java/org/asynchttpclient/AsyncHandler.java deleted file mode 100644 index 843868e3f3..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncHandler.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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; - -/** - * An asynchronous handler or callback which gets invoked as soon as some data is available when - * processing an asynchronous response.
- * Callback methods get invoked in the following order: - *

    - *
  1. {@link #onStatusReceived(HttpResponseStatus)},
  2. - *
  3. {@link #onHeadersReceived(HttpResponseHeaders)},
  4. - *
  5. {@link #onBodyPartReceived(HttpResponseBodyPart)}, which could be invoked multiple times,
  6. - *
  7. {@link #onCompleted()}, once the response has been fully read.
  8. - *
- *

- * Returning a {@link AsyncHandler.STATE#ABORT} from any of those callback methods will interrupt asynchronous response - * processing, after that only {@link #onCompleted()} is going to be called. - *

- *

- * AsyncHandler aren't thread safe, hence you should avoid re-using the same instance when doing concurrent requests. - * As an exmaple, the following may produce unexpected results: - *

- *   AsyncHandler ah = new AsyncHandler() {....};
- *   AsyncHttpClient client = new AsyncHttpClient();
- *   client.prepareGet("http://.../").execute(ah);
- *   client.prepareGet("http://.../").execute(ah);
- * 
- * It is recommended to create a new instance instead. - * - * @param Type of object returned by the {@link java.util.concurrent.Future#get} - */ -public interface AsyncHandler { - - public static enum STATE { - - /** - * Stop the processing. - */ - ABORT, - /** - * Continue the processing - */ - CONTINUE, - /** - * Upgrade the protocol. - */ - UPGRADE - } - - /** - * Invoked when an unexpected exception occurs during the processing of the response. The exception may have been - * produced by implementation of onXXXReceived method invocation. - * - * @param t a {@link Throwable} - */ - void onThrowable(Throwable t); - - /** - * Invoked as soon as some response body part are received. Could be invoked many times. - * Beware that, depending on the provider (Netty) this can be notified with empty body parts. - * - * @param bodyPart response's body part. - * @return a {@link STATE} telling to CONTINUE or ABORT the current processing. - * @throws Exception if something wrong happens - */ - STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception; - - /** - * Invoked as soon as the HTTP status line has been received - * - * @param responseStatus the status code and test of the response - * @return a {@link STATE} telling to CONTINUE or ABORT the current processing. - * @throws Exception if something wrong happens - */ - STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception; - - /** - * Invoked as soon as the HTTP headers has been received. Can potentially be invoked more than once if a broken server - * sent trailing headers. - * - * @param headers the HTTP headers. - * @return a {@link STATE} telling to CONTINUE or ABORT the current processing. - * @throws Exception if something wrong happens - */ - STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception; - - /** - * Invoked once the HTTP response processing is finished. - *

- *

- * Gets always invoked as last callback method. - * - * @return T Value that will be returned by the associated {@link java.util.concurrent.Future} - * @throws Exception if something wrong happens - */ - T onCompleted() throws Exception; -} diff --git a/api/src/main/java/org/asynchttpclient/AsyncHandlerExtensions.java b/api/src/main/java/org/asynchttpclient/AsyncHandlerExtensions.java deleted file mode 100644 index 1d81570b0f..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncHandlerExtensions.java +++ /dev/null @@ -1,60 +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; - -/** - * This interface hosts new low level callback methods on {@link AsyncHandler}. - * For now, those methods are in a dedicated interface in order not to break the existing API, - * but could be merged into one of the existing ones in AHC 2. - * - * More additional hooks might come, such as: - *

    - *
  • onConnectionClosed()
  • - *
  • onBytesSent(long numberOfBytes)
  • - *
  • onBytesReceived(long numberOfBytes)
  • - *
- */ -public interface AsyncHandlerExtensions { - - /** - * Notify the callback when trying to open a new connection. - */ - void onOpenConnection(); - - /** - * Notify the callback when a new connection was successfully opened. - */ - void onConnectionOpen(); - - /** - * Notify the callback when trying to fetch a connection from the pool. - */ - void onPoolConnection(); - - /** - * Notify the callback when a new connection was successfully fetched from the pool. - */ - void onConnectionPooled(); - - /** - * Notify the callback when a request is being written on the wire. - * If the original request causes multiple requests to be sent, for example, because of authorization or retry, - * it will be notified multiple times. - */ - void onSendRequest(); - - /** - * Notify the callback every time a request is being retried. - */ - void onRetry(); -} diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClient.java deleted file mode 100755 index 45136c2971..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClient.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 java.io.Closeable; -import java.io.IOException; -import java.util.concurrent.Future; - -/** - * This class support asynchronous and synchronous HTTP request. - *

- * To execute synchronous HTTP request, you just need to do - *

- *    AsyncHttpClient c = new AsyncHttpClient();
- *    Future f = c.prepareGet("http://www.ning.com/").execute();
- * 
- * The code above will block until the response is fully received. To execute asynchronous HTTP request, you - * create an {@link AsyncHandler} or its abstract implementation, {@link AsyncCompletionHandler} - *

- *

- *       AsyncHttpClient c = new AsyncHttpClient();
- *       Future f = c.prepareGet("http://www.ning.com/").execute(new AsyncCompletionHandler() {
- * 

- * @Override - * public Response onCompleted(Response response) throws IOException { - * // Do something - * return response; - * } - *

- * @Override - * public void onThrowable(Throwable t) { - * } - * }); - * Response response = f.get(); - *

- * // We are just interested to retrieve the status code. - * Future f = c.prepareGet("http://www.ning.com/").execute(new AsyncCompletionHandler() { - *

- * @Override - * public Integer onCompleted(Response response) throws IOException { - * // Do something - * return response.getStatusCode(); - * } - *

- * @Override - * public void onThrowable(Throwable t) { - * } - * }); - * Integer statusCode = f.get(); - *

- * You can also have more control about the how the response is asynchronously processed by using a {@link AsyncHandler} - *
- *      AsyncHttpClient c = new AsyncHttpClient();
- *      Future f = c.prepareGet("http://www.ning.com/").execute(new AsyncHandler() {
- *          private StringBuilder builder = new StringBuilder();
- * 

- * @Override - * public STATE onStatusReceived(HttpResponseStatus s) throws Exception { - * // return STATE.CONTINUE or STATE.ABORT - * return STATE.CONTINUE - * } - *

- * @Override - * public STATE onHeadersReceived(HttpResponseHeaders bodyPart) throws Exception { - * // return STATE.CONTINUE or STATE.ABORT - * return STATE.CONTINUE - *

- * } - * @Override - *

- * public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - * builder.append(new String(bodyPart)); - * // return STATE.CONTINUE or STATE.ABORT - * return STATE.CONTINUE - * } - *

- * @Override - * public String onCompleted() throws Exception { - * // Will be invoked once the response has been fully read or a ResponseComplete exception - * // has been thrown. - * return builder.toString(); - * } - *

- * @Override - * public void onThrowable(Throwable t) { - * } - * }); - *

- * String bodyResponse = f.get(); - *

- * This class can also be used without the need of {@link AsyncHandler}

- *
- *      AsyncHttpClient c = new AsyncHttpClient();
- *      Future f = c.prepareGet(TARGET_URL).execute();
- *      Response r = f.get();
- * 
- *

- * Finally, you can configure the AsyncHttpClient using an {@link AsyncHttpClientConfig} instance

- *
- *      AsyncHttpClient c = new AsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(...).build());
- *      Future f = c.prepareGet(TARGET_URL).execute();
- *      Response r = f.get();
- * 
- *

- * An instance of this class will cache every HTTP 1.1 connections and close them when the {@link AsyncHttpClientConfig#getIdleConnectionTimeoutInMs()} - * expires. This object can hold many persistent connections to different host. - */ -public interface AsyncHttpClient extends Closeable { - - /** - * Return the asynchronous {@link AsyncHttpProvider} - * - * @return an {@link AsyncHttpProvider} - */ - AsyncHttpProvider getProvider(); - - /** - * Close the underlying connections. - */ - void close(); - - /** - * Asynchronous close the {@link AsyncHttpProvider} by spawning a thread and avoid blocking. - */ - void closeAsynchronously(); - - /** - * Return true if closed - * - * @return true if closed - */ - boolean isClosed(); - - /** - * Return the {@link AsyncHttpClientConfig} - * - * @return {@link AsyncHttpClientConfig} - */ - AsyncHttpClientConfig getConfig(); - - /** - * Set default signature calculator to use for requests build by this client instance - */ - AsyncHttpClient setSignatureCalculator(SignatureCalculator signatureCalculator); - - /** - * Prepare an HTTP client GET request. - * - * @param url A well formed URL. - * @return {@link RequestBuilder} - */ - BoundRequestBuilder prepareGet(String url); - - /** - * Prepare an HTTP client CONNECT request. - * - * @param url A well formed URL. - * @return {@link RequestBuilder} - */ - BoundRequestBuilder prepareConnect(String url); - - /** - * Prepare an HTTP client OPTIONS request. - * - * @param url A well formed URL. - * @return {@link RequestBuilder} - */ - BoundRequestBuilder prepareOptions(String url); - - /** - * Prepare an HTTP client HEAD request. - * - * @param url A well formed URL. - * @return {@link RequestBuilder} - */ - BoundRequestBuilder prepareHead(String url); - - /** - * Prepare an HTTP client POST request. - * - * @param url A well formed URL. - * @return {@link RequestBuilder} - */ - BoundRequestBuilder preparePost(String url); - - /** - * Prepare an HTTP client PUT request. - * - * @param url A well formed URL. - * @return {@link RequestBuilder} - */ - BoundRequestBuilder preparePut(String url); - - /** - * Prepare an HTTP client DELETE request. - * - * @param url A well formed URL. - * @return {@link RequestBuilder} - */ - BoundRequestBuilder prepareDelete(String url); - - /** - * Prepare an HTTP client PATCH request. - * - * @param url A well formed URL. - * @return {@link RequestBuilder} - */ - BoundRequestBuilder preparePatch(String url); - - /** - * Prepare an HTTP client TRACE request. - * - * @param url A well formed URL. - * @return {@link RequestBuilder} - */ - BoundRequestBuilder prepareTrace(String url); - - /** - * Construct a {@link RequestBuilder} using a {@link Request} - * - * @param request a {@link Request} - * @return {@link RequestBuilder} - */ - BoundRequestBuilder prepareRequest(Request request); - - /** - * Execute an HTTP request. - * - * @param request {@link Request} - * @param handler an instance of {@link AsyncHandler} - * @param Type of the value that will be returned by the associated {@link java.util.concurrent.Future} - * @return a {@link Future} of type T - * @throws IOException - */ - ListenableFuture executeRequest(Request request, AsyncHandler handler) throws IOException; - - /** - * Execute an HTTP request. - * - * @param request {@link Request} - * @return a {@link Future} of type Response - * @throws IOException - */ - ListenableFuture executeRequest(Request request) throws IOException; -} diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java deleted file mode 100644 index 2a14d03f32..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ /dev/null @@ -1,1136 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 static org.asynchttpclient.AsyncHttpClientConfigDefaults.*; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Properties; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLContext; - -import org.asynchttpclient.date.TimeConverter; -import org.asynchttpclient.filter.IOExceptionFilter; -import org.asynchttpclient.filter.RequestFilter; -import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.util.DefaultHostnameVerifier; -import org.asynchttpclient.util.ProxyUtils; - -/** - * Configuration class to use with a {@link AsyncHttpClient}. System property can be also used to configure this - * object default behavior by doing: - *

- * -Dorg.asynchttpclient.AsyncHttpClientConfig.nameOfTheProperty - */ -public class AsyncHttpClientConfig { - - protected final static String ASYNC_CLIENT = AsyncHttpClientConfig.class.getName() + "."; - public final static String AHC_VERSION; - - static { - InputStream is = null; - Properties prop = new Properties(); - try { - is = AsyncHttpClientConfig.class.getResourceAsStream("version.properties"); - prop.load(is); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException ignored) { - - } - } - } - AHC_VERSION = prop.getProperty("ahc.version", "UNKNOWN"); - } - - protected int connectionTimeout; - - protected int maxConnections; - protected int maxConnectionsPerHost; - - protected int requestTimeout; - protected int readTimeout; - protected int webSocketTimeout; - - protected boolean allowPoolingConnections; - protected boolean allowPoolingSslConnections; - protected int pooledConnectionIdleTimeout; - protected int connectionTTL; - - protected SSLContext sslContext; - protected HostnameVerifier hostnameVerifier; - protected boolean acceptAnyCertificate; - - protected boolean followRedirect; - protected int maxRedirects; - protected boolean removeQueryParamOnRedirect; - protected boolean strict302Handling; - - protected ProxyServerSelector proxyServerSelector; - protected boolean useRelativeURIsWithConnectProxies; - - protected boolean compressionEnabled; - protected String userAgent; - protected ExecutorService applicationThreadPool; - protected Realm realm; - protected List requestFilters; - protected List responseFilters; - protected List ioExceptionFilters; - protected int maxRequestRetry; - protected boolean disableUrlEncodingForBoundRequests; - protected int ioThreadMultiplier; - protected TimeConverter timeConverter; - protected AsyncHttpProviderConfig providerConfig; - - // AHC 2 specific - protected boolean spdyEnabled; - protected int spdyInitialWindowSize; - protected int spdyMaxConcurrentStreams; - - protected AsyncHttpClientConfig() { - } - - private AsyncHttpClientConfig(int connectionTimeout,// - int maxConnections,// - int maxConnectionsPerHost,// - int requestTimeout,// - int readTimeout,// - int webSocketTimeout,// - boolean allowPoolingConnection,// - boolean allowSslConnectionPool,// - int idleConnectionInPoolTimeout,// - int maxConnectionLifeTime,// - SSLContext sslContext, // - HostnameVerifier hostnameVerifier,// - boolean acceptAnyCertificate, // - boolean followRedirect, // - int maxRedirects, // - boolean removeQueryParamOnRedirect,// - boolean strict302Handling, // - ExecutorService applicationThreadPool,// - ProxyServerSelector proxyServerSelector, // - boolean useRelativeURIsWithConnectProxies, // - boolean compressionEnabled, // - String userAgent,// - Realm realm,// - List requestFilters,// - List responseFilters,// - List ioExceptionFilters,// - int maxRequestRetry, // - boolean disableUrlEncodingForBoundRequests, // - int ioThreadMultiplier, // - TimeConverter timeConverter,// - AsyncHttpProviderConfig providerConfig,// - boolean spdyEnabled, // - int spdyInitialWindowSize, // - int spdyMaxConcurrentStreams) { - - this.connectionTimeout = connectionTimeout; - this.maxConnections = maxConnections; - this.maxConnectionsPerHost = maxConnectionsPerHost; - this.requestTimeout = requestTimeout; - this.readTimeout = readTimeout; - this.webSocketTimeout = webSocketTimeout; - this.allowPoolingConnections = allowPoolingConnection; - this.allowPoolingSslConnections = allowSslConnectionPool; - this.pooledConnectionIdleTimeout = idleConnectionInPoolTimeout; - this.connectionTTL = maxConnectionLifeTime; - this.sslContext = sslContext; - this.hostnameVerifier = hostnameVerifier; - this.acceptAnyCertificate = acceptAnyCertificate; - this.followRedirect = followRedirect; - this.maxRedirects = maxRedirects; - this.removeQueryParamOnRedirect = removeQueryParamOnRedirect; - this.strict302Handling = strict302Handling; - this.proxyServerSelector = proxyServerSelector; - this.useRelativeURIsWithConnectProxies = useRelativeURIsWithConnectProxies; - this.compressionEnabled = compressionEnabled; - this.userAgent = userAgent; - this.applicationThreadPool = applicationThreadPool == null ? Executors.newCachedThreadPool() : applicationThreadPool; - this.realm = realm; - this.requestFilters = requestFilters; - this.responseFilters = responseFilters; - this.ioExceptionFilters = ioExceptionFilters; - this.maxRequestRetry = maxRequestRetry; - this.disableUrlEncodingForBoundRequests = disableUrlEncodingForBoundRequests; - this.ioThreadMultiplier = ioThreadMultiplier; - this.timeConverter = timeConverter; - this.providerConfig = providerConfig; - this.spdyEnabled = spdyEnabled; - this.spdyInitialWindowSize = spdyInitialWindowSize; - this.spdyMaxConcurrentStreams = spdyMaxConcurrentStreams; - - } - - /** - * Return the maximum number of connections an {@link AsyncHttpClient} can handle. - * - * @return the maximum number of connections an {@link AsyncHttpClient} can handle. - */ - public int getMaxConnections() { - return maxConnections; - } - - /** - * Return the maximum number of connections per hosts an {@link AsyncHttpClient} can handle. - * - * @return the maximum number of connections per host an {@link AsyncHttpClient} can handle. - */ - public int getMaxConnectionsPerHost() { - return maxConnectionsPerHost; - } - - /** - * Return the maximum time in millisecond an {@link AsyncHttpClient} can wait when connecting to a remote host - * - * @return the maximum time in millisecond an {@link AsyncHttpClient} can wait when connecting to a remote host - */ - public int getConnectionTimeout() { - return connectionTimeout; - } - - /** - * Return the maximum time, in milliseconds, a {@link org.asynchttpclient.websocket.WebSocket} may be idle before being timed out. - * @return the maximum time, in milliseconds, a {@link org.asynchttpclient.websocket.WebSocket} may be idle before being timed out. - */ - public int getWebSocketTimeout() { - return webSocketTimeout; - } - - /** - * Return the maximum time in millisecond an {@link AsyncHttpClient} can stay idle. - * - * @return the maximum time in millisecond an {@link AsyncHttpClient} can stay idle. - */ - public int getReadTimeout() { - return readTimeout; - } - - /** - * Return the maximum time in millisecond an {@link AsyncHttpClient} will keep connection - * in pool. - * - * @return the maximum time in millisecond an {@link AsyncHttpClient} will keep connection - * in pool. - */ - public int getPooledConnectionIdleTimeout() { - return pooledConnectionIdleTimeout; - } - - /** - * Return the maximum time in millisecond an {@link AsyncHttpClient} wait for a response - * - * @return the maximum time in millisecond an {@link AsyncHttpClient} wait for a response - */ - public int getRequestTimeout() { - return requestTimeout; - } - - /** - * Is HTTP redirect enabled - * - * @return true if enabled. - */ - public boolean isFollowRedirect() { - return followRedirect; - } - - /** - * Get the maximum number of HTTP redirect - * - * @return the maximum number of HTTP redirect - */ - public int getMaxRedirects() { - return maxRedirects; - } - - /** - * Is the {@link ConnectionsPool} support enabled. - * - * @return true if keep-alive is enabled - */ - public boolean isAllowPoolingConnections() { - return allowPoolingConnections; - } - - /** - * Return the USER_AGENT header value - * - * @return the USER_AGENT header value - */ - public String getUserAgent() { - return userAgent; - } - - /** - * Is HTTP compression enabled. - * - * @return true if compression is enabled - */ - public boolean isCompressionEnabled() { - return compressionEnabled; - } - - /** - * Return the {@link java.util.concurrent.ExecutorService} an {@link AsyncHttpClient} use for handling - * asynchronous response. - * - * @return the {@link java.util.concurrent.ExecutorService} an {@link AsyncHttpClient} use for handling - * asynchronous response. If no {@link ExecutorService} has been explicitly provided, - * this method will return null - */ - public ExecutorService executorService() { - return applicationThreadPool; - } - - /** - * An instance of {@link ProxyServer} used by an {@link AsyncHttpClient} - * - * @return instance of {@link ProxyServer} - */ - public ProxyServerSelector getProxyServerSelector() { - return proxyServerSelector; - } - - /** - * Return an instance of {@link SSLContext} used for SSL connection. - * - * @return an instance of {@link SSLContext} used for SSL connection. - */ - public SSLContext getSSLContext() { - return sslContext; - } - - /** - * Return the {@link AsyncHttpProviderConfig} - * - * @return the {@link AsyncHttpProviderConfig} - */ - public AsyncHttpProviderConfig getAsyncHttpProviderConfig() { - return providerConfig; - } - - /** - * Return the current {@link Realm}} - * - * @return the current {@link Realm}} - */ - public Realm getRealm() { - return realm; - } - - /** - * @return true if {@link RequestFilter}s have been defined. - * - * @since 2.0.0 - */ - public boolean hasRequestFilters() { - return !requestFilters.isEmpty(); - } - - /** - * Return the list of {@link RequestFilter} - * - * @return Unmodifiable list of {@link ResponseFilter} - */ - public List getRequestFilters() { - return Collections.unmodifiableList(requestFilters); - } - - /** - * @return true if {@link ResponseFilter}s have been defined. - * @since 2.0.0 - */ - public boolean hasResponseFilters() { - return !responseFilters.isEmpty(); - } - - /** - * Return the list of {@link ResponseFilter} - * - * @return Unmodifiable list of {@link ResponseFilter} - */ - public List getResponseFilters() { - return Collections.unmodifiableList(responseFilters); - } - - /** - * Return the list of {@link java.io.IOException} - * - * @return Unmodifiable list of {@link java.io.IOException} - */ - public List getIOExceptionFilters() { - return Collections.unmodifiableList(ioExceptionFilters); - } - - /** - * Return the number of time the library will retry when an {@link java.io.IOException} is throw by the remote server - * - * @return the number of time the library will retry when an {@link java.io.IOException} is throw by the remote server - */ - public int getMaxRequestRetry() { - return maxRequestRetry; - } - - /** - * Return true is SSL connection polling is enabled. Default is true. - * - * @return true is enabled. - */ - public boolean isAllowPoolingSslConnections() { - return allowPoolingSslConnections; - } - - /** - * @return the disableUrlEncodingForBoundRequests - */ - public boolean isDisableUrlEncodingForBoundRequests() { - return disableUrlEncodingForBoundRequests; - } - - /** - * @return whether or not SPDY is enabled. - */ - public boolean isSpdyEnabled() { - return spdyEnabled; - } - - /** - * @return the windows size new SPDY sessions should be initialized to. - */ - public int getSpdyInitialWindowSize() { - return spdyInitialWindowSize; - } - - /** - * @return the maximum number of concurrent streams over one SPDY session. - */ - public int getSpdyMaxConcurrentStreams() { - return spdyMaxConcurrentStreams; - } - - /** - * Return true if the query parameters will be stripped from the request when a redirect is requested. - * - * @return true if the query parameters will be stripped from the request when a redirect is requested. - */ - public boolean isRemoveQueryParamOnRedirect() { - return removeQueryParamOnRedirect; - } - - /** - * @return true if both the application and reaper thread pools - * haven't yet been shutdown. - * @since 1.7.21 - */ - public boolean isValid() { - boolean atpRunning = true; - try { - atpRunning = applicationThreadPool.isShutdown(); - } catch (Exception ignore) { - // isShutdown() will thrown an exception in an EE7 environment - // when using a ManagedExecutorService. - // When this is the case, we assume it's running. - } - return atpRunning; - } - - /** - * Return the {@link HostnameVerifier} - * - * @return the {@link HostnameVerifier} - */ - public HostnameVerifier getHostnameVerifier() { - return hostnameVerifier; - } - - /** - * @return number to multiply by availableProcessors() that will determine # of NioWorkers to use - */ - public int getIoThreadMultiplier() { - return ioThreadMultiplier; - } - - /** - *

- * In the case of a POST/Redirect/Get scenario where the server uses a 302 - * for the redirect, should AHC respond to the redirect with a GET or - * whatever the original method was. Unless configured otherwise, - * for a 302, AHC, will use a GET for this case. - *

- * - * @return true if string 302 handling is to be used, - * otherwise false. - * - * @since 1.7.2 - */ - public boolean isStrict302Handling() { - return strict302Handling; - } - - /** - * @returntrue if AHC should use relative URIs instead of absolute ones when talking with a SSL proxy - * or WebSocket proxy, otherwise false. - * - * @since 1.8.13 - */ - public boolean isUseRelativeURIsWithConnectProxies() { - return useRelativeURIsWithConnectProxies; - } - - /** - * Return the maximum time in millisecond an {@link AsyncHttpClient} will keep connection in the pool, or -1 to keep connection while possible. - * - * @return the maximum time in millisecond an {@link AsyncHttpClient} will keep connection in the pool, or -1 to keep connection while possible. - */ - public int getConnectionTTL() { - return connectionTTL; - } - - /** - * @return the TimeConverter used for converting RFC2616Dates into time - * - * @since 2.0.0 - */ - public TimeConverter getTimeConverter() { - return timeConverter; - } - - public boolean isAcceptAnyCertificate() { - return acceptAnyCertificate; - } - - /** - * Builder for an {@link AsyncHttpClient} - */ - public static class Builder { - private int connectionTimeout = defaultConnectionTimeout(); - private int maxConnections = defaultMaxConnections(); - private int maxConnectionsPerHost = defaultMaxConnectionsPerHost(); - private int requestTimeout = defaultRequestTimeout(); - private int readTimeout = defaultReadTimeout(); - private int webSocketTimeout = defaultWebSocketTimeout(); - private boolean allowPoolingConnections = defaultAllowPoolingConnections(); - private boolean allowPoolingSslConnections = defaultAllowPoolingSslConnections(); - private int pooledConnectionIdleTimeout = defaultPooledConnectionIdleTimeout(); - private int connectionTTL = defaultConnectionTTL(); - private SSLContext sslContext; - private HostnameVerifier hostnameVerifier; - private boolean acceptAnyCertificate = defaultAcceptAnyCertificate(); - private boolean followRedirect = defaultFollowRedirect(); - private int maxRedirects = defaultMaxRedirects(); - private boolean removeQueryParamOnRedirect = defaultRemoveQueryParamOnRedirect(); - private boolean strict302Handling = defaultStrict302Handling(); - private ProxyServerSelector proxyServerSelector = null; - private boolean useProxySelector = defaultUseProxySelector(); - private boolean useProxyProperties = defaultUseProxyProperties(); - private boolean useRelativeURIsWithConnectProxies = defaultUseRelativeURIsWithConnectProxies(); - private boolean compressionEnabled = defaultCompressionEnabled(); - private String userAgent = defaultUserAgent(); - private ExecutorService applicationThreadPool; - private Realm realm; - private final List requestFilters = new LinkedList(); - private final List responseFilters = new LinkedList(); - private final List ioExceptionFilters = new LinkedList(); - private int maxRequestRetry = defaultMaxRequestRetry(); - private boolean disableUrlEncodingForBoundRequests = defaultDisableUrlEncodingForBoundRequests(); - private int ioThreadMultiplier = defaultIoThreadMultiplier(); - private TimeConverter timeConverter; - private AsyncHttpProviderConfig providerConfig; - - // AHC 2 - private boolean spdyEnabled = defaultSpdyEnabled(); - private int spdyInitialWindowSize = defaultSpdyInitialWindowSize(); - private int spdyMaxConcurrentStreams = defaultSpdyMaxConcurrentStreams(); - - public Builder() { - } - - /** - * Set the maximum number of connections an {@link AsyncHttpClient} can handle. - * - * @param maxConnections the maximum number of connections an {@link AsyncHttpClient} can handle. - * @return a {@link Builder} - */ - public Builder setMaxConnections(int maxConnections) { - this.maxConnections = maxConnections; - return this; - } - - /** - * Set the maximum number of connections per hosts an {@link AsyncHttpClient} can handle. - * - * @param maxConnectionsPerHost the maximum number of connections per host an {@link AsyncHttpClient} can handle. - * @return a {@link Builder} - */ - public Builder setMaxConnectionsPerHost(int maxConnectionsPerHost) { - this.maxConnectionsPerHost = maxConnectionsPerHost; - return this; - } - - /** - * Set the maximum time in millisecond an {@link AsyncHttpClient} can wait when connecting to a remote host - * - * @param connectionTimeout the maximum time in millisecond an {@link AsyncHttpClient} can wait when connecting to a remote host - * @return a {@link Builder} - */ - public Builder setConnectionTimeout(int connectionTimeout) { - this.connectionTimeout = connectionTimeout; - return this; - } - - /** - * Set the maximum time in millisecond an {@link org.asynchttpclient.websocket.WebSocket} can stay idle. - * - * @param webSocketTimeout - * the maximum time in millisecond an {@link org.asynchttpclient.websocket.WebSocket} can stay idle. - * @return a {@link Builder} - */ - public Builder setWebSocketTimeout(int webSocketTimeout) { - this.webSocketTimeout = webSocketTimeout; - return this; - } - - /** - * Set the maximum time in millisecond an {@link AsyncHttpClient} can stay idle. - * - * @param readTimeout - * the maximum time in millisecond an {@link AsyncHttpClient} can stay idle. - * @return a {@link Builder} - */ - public Builder setReadTimeout(int readTimeout) { - this.readTimeout = readTimeout; - return this; - } - - /** - * Set the maximum time in millisecond an {@link AsyncHttpClient} will keep connection - * idle in pool. - * - * @param pooledConnectionIdleTimeout - * the maximum time in millisecond an {@link AsyncHttpClient} will keep connection - * idle in pool. - * @return a {@link Builder} - */ - public Builder setPooledConnectionIdleTimeout(int pooledConnectionIdleTimeout) { - this.pooledConnectionIdleTimeout = pooledConnectionIdleTimeout; - return this; - } - - /** - * Set the maximum time in millisecond an {@link AsyncHttpClient} wait for a response - * - * @param requestTimeout the maximum time in millisecond an {@link AsyncHttpClient} wait for a response - * @return a {@link Builder} - */ - public Builder setRequestTimeout(int requestTimeout) { - this.requestTimeout = requestTimeout; - return this; - } - - /** - * Set to true to enable HTTP redirect - * - * @param redirectEnabled true if enabled. - * @return a {@link Builder} - */ - public Builder setFollowRedirect(boolean followRedirect) { - this.followRedirect = followRedirect; - return this; - } - - /** - * Set the maximum number of HTTP redirect - * - * @param maxRedirects the maximum number of HTTP redirect - * @return a {@link Builder} - */ - public Builder setMaxRedirects(int maxRedirects) { - this.maxRedirects = maxRedirects; - return this; - } - - /** - * Enable HTTP compression. - * - * @param compressionEnabled true if compression is enabled - * @return a {@link Builder} - */ - public Builder setCompressionEnabled(boolean compressionEnabled) { - this.compressionEnabled = compressionEnabled; - return this; - } - - /** - * Set the USER_AGENT header value - * - * @param userAgent the USER_AGENT header value - * @return a {@link Builder} - */ - public Builder setUserAgent(String userAgent) { - this.userAgent = userAgent; - return this; - } - - /** - * Set true if connection can be pooled by a {@link ConnectionsPool}. Default is true. - * - * @param allowPoolingConnections true if connection can be pooled by a {@link ConnectionsPool} - * @return a {@link Builder} - */ - public Builder setAllowPoolingConnections(boolean allowPoolingConnections) { - this.allowPoolingConnections = allowPoolingConnections; - return this; - } - - /** - * Set the {@link java.util.concurrent.ExecutorService} an {@link AsyncHttpClient} use for handling - * asynchronous response. - * - * @param applicationThreadPool the {@link java.util.concurrent.ExecutorService} an {@link AsyncHttpClient} use for handling - * asynchronous response. - * @return a {@link Builder} - */ - public Builder setExecutorService(ExecutorService applicationThreadPool) { - this.applicationThreadPool = applicationThreadPool; - return this; - } - - /** - * Set an instance of {@link ProxyServerSelector} used by an {@link AsyncHttpClient} - * - * @param proxyServerSelector instance of {@link ProxyServerSelector} - * @return a {@link Builder} - */ - public Builder setProxyServerSelector(ProxyServerSelector proxyServerSelector) { - this.proxyServerSelector = proxyServerSelector; - return this; - } - - /** - * Set an instance of {@link ProxyServer} used by an {@link AsyncHttpClient} - * - * @param proxyServer instance of {@link ProxyServer} - * @return a {@link Builder} - */ - public Builder setProxyServer(ProxyServer proxyServer) { - this.proxyServerSelector = ProxyUtils.createProxyServerSelector(proxyServer); - return this; - } - - /** - * Set the {@link SSLContext} for secure connection. - * - * @param sslContext the {@link SSLContext} for secure connection - * @return a {@link Builder} - */ - public Builder setSSLContext(final SSLContext sslContext) { - this.sslContext = sslContext; - return this; - } - - /** - * Set the {@link AsyncHttpProviderConfig} - * - * @param providerConfig the {@link AsyncHttpProviderConfig} - * @return a {@link Builder} - */ - public Builder setAsyncHttpClientProviderConfig(AsyncHttpProviderConfig providerConfig) { - this.providerConfig = providerConfig; - return this; - } - - /** - * Set the {@link Realm} that will be used for all requests. - * - * @param realm the {@link Realm} - * @return a {@link Builder} - */ - public Builder setRealm(Realm realm) { - this.realm = realm; - return this; - } - - /** - * Add an {@link org.asynchttpclient.filter.RequestFilter} that will be invoked before {@link AsyncHttpClient#executeRequest(Request)} - * - * @param requestFilter {@link org.asynchttpclient.filter.RequestFilter} - * @return this - */ - public Builder addRequestFilter(RequestFilter requestFilter) { - requestFilters.add(requestFilter); - return this; - } - - /** - * Remove an {@link org.asynchttpclient.filter.RequestFilter} that will be invoked before {@link AsyncHttpClient#executeRequest(Request)} - * - * @param requestFilter {@link org.asynchttpclient.filter.RequestFilter} - * @return this - */ - public Builder removeRequestFilter(RequestFilter requestFilter) { - requestFilters.remove(requestFilter); - return this; - } - - /** - * Add an {@link org.asynchttpclient.filter.ResponseFilter} that will be invoked as soon as the response is - * received, and before {@link AsyncHandler#onStatusReceived(HttpResponseStatus)}. - * - * @param responseFilter an {@link org.asynchttpclient.filter.ResponseFilter} - * @return this - */ - public Builder addResponseFilter(ResponseFilter responseFilter) { - responseFilters.add(responseFilter); - return this; - } - - /** - * Remove an {@link org.asynchttpclient.filter.ResponseFilter} that will be invoked as soon as the response is - * received, and before {@link AsyncHandler#onStatusReceived(HttpResponseStatus)}. - * - * @param responseFilter an {@link org.asynchttpclient.filter.ResponseFilter} - * @return this - */ - public Builder removeResponseFilter(ResponseFilter responseFilter) { - responseFilters.remove(responseFilter); - return this; - } - - /** - * Add an {@link org.asynchttpclient.filter.IOExceptionFilter} that will be invoked when an {@link java.io.IOException} - * occurs during the download/upload operations. - * - * @param ioExceptionFilter an {@link org.asynchttpclient.filter.ResponseFilter} - * @return this - */ - public Builder addIOExceptionFilter(IOExceptionFilter ioExceptionFilter) { - ioExceptionFilters.add(ioExceptionFilter); - return this; - } - - /** - * Remove an {@link org.asynchttpclient.filter.IOExceptionFilter} tthat will be invoked when an {@link java.io.IOException} - * occurs during the download/upload operations. - * - * @param ioExceptionFilter an {@link org.asynchttpclient.filter.ResponseFilter} - * @return this - */ - public Builder removeIOExceptionFilter(IOExceptionFilter ioExceptionFilter) { - ioExceptionFilters.remove(ioExceptionFilter); - return this; - } - - /** - * Set the number of times a request will be retried when an {@link java.io.IOException} occurs because of a Network exception. - * - * @param maxRequestRetry the number of times a request will be retried - * @return this - */ - public Builder setMaxRequestRetry(int maxRequestRetry) { - this.maxRequestRetry = maxRequestRetry; - return this; - } - - /** - * Return true is if connections pooling is enabled. - * - * @param pooledConnectionIdleTimeout true if enabled - * @return this - */ - public Builder setAllowPoolingSslConnections(boolean allowPoolingSslConnections) { - this.allowPoolingSslConnections = allowPoolingSslConnections; - return this; - } - - /** - * Allows use unescaped URLs in requests - * useful for retrieving data from broken sites - * - * @param disableUrlEncodingForBoundRequests - * @return this - */ - public Builder setDisableUrlEncodingForBoundRequests(boolean disableUrlEncodingForBoundRequests) { - this.disableUrlEncodingForBoundRequests = disableUrlEncodingForBoundRequests; - return this; - } - - /** - * Set to false if you don't want the query parameters removed when a redirect occurs. - * - * @param removeQueryParamOnRedirect - * @return this - */ - public Builder setRemoveQueryParamsOnRedirect(boolean removeQueryParamOnRedirect) { - this.removeQueryParamOnRedirect = removeQueryParamOnRedirect; - return this; - } - - /** - * Sets whether AHC should use the default JDK ProxySelector to select a proxy server. - *

- * If useProxySelector is set to true but {@link #setProxyServer(ProxyServer)} - * was used to explicitly set a proxy server, the latter is preferred. - *

- * See http://docs.oracle.com/javase/7/docs/api/java/net/ProxySelector.html - */ - public Builder setUseProxySelector(boolean useProxySelector) { - this.useProxySelector = useProxySelector; - return this; - } - - /** - * Sets whether AHC should use the default http.proxy* system properties - * to obtain proxy information. This differs from {@link #setUseProxySelector(boolean)} - * in that AsyncHttpClient will use its own logic to handle the system properties, - * potentially supporting other protocols that the the JDK ProxySelector doesn't. - *

- * If useProxyProperties is set to true but {@link #setUseProxySelector(boolean)} - * was also set to true, the latter is preferred. - *

- * See http://download.oracle.com/javase/1.4.2/docs/guide/net/properties.html - */ - public Builder setUseProxyProperties(boolean useProxyProperties) { - this.useProxyProperties = useProxyProperties; - return this; - } - - public Builder setIOThreadMultiplier(int multiplier) { - this.ioThreadMultiplier = multiplier; - return this; - } - - /** - * Set the {@link HostnameVerifier} - * - * @param hostnameVerifier {@link HostnameVerifier} - * @return this - */ - public Builder setHostnameVerifier(HostnameVerifier hostnameVerifier) { - this.hostnameVerifier = hostnameVerifier; - return this; - } - - /** - * Configures this AHC instance to be strict in it's handling of 302 redirects - * in a POST/Redirect/GET situation. - * - * @param strict302Handling strict handling - * - * @return this - * - * @since 1.7.2 - */ - public Builder setStrict302Handling(final boolean strict302Handling) { - this.strict302Handling = strict302Handling; - return this; - } - - /** - * Set the maximum time in millisecond connection can be added to the pool for further reuse - * - * @param connectionTTL the maximum time in millisecond connection can be added to the pool for further reuse - * @return a {@link Builder} - */ - public Builder setConnectionTTL(int connectionTTL) { - this.connectionTTL = connectionTTL; - return this; - } - - /** - * Configures this AHC instance to use relative URIs instead of absolute ones when talking with a SSL or WebSocket proxy. - * - * @param useRelativeURIsWithConnectProxies - * @return this - * - * @since 1.7.2 - */ - public Builder setUseRelativeURIsWithConnectProxies(boolean useRelativeURIsWithConnectProxies) { - this.useRelativeURIsWithConnectProxies = useRelativeURIsWithConnectProxies; - return this; - } - - /** - * Enables SPDY support. Note that doing so, will currently disable WebSocket support - * for this client instance. If not explicitly enabled, spdy will not be used. - * - * @param spdyEnabled configures spdy support. - * - * @return this - * - * @since 2.0 - */ - public Builder setSpdyEnabled(boolean spdyEnabled) { - this.spdyEnabled = spdyEnabled; - return this; - } - - /** - * Configures the initial window size for the SPDY session. - * - * @param spdyInitialWindowSize the initial window size. - * - * @return this - * - * @since 2.0 - */ - public Builder setSpdyInitialWindowSize(int spdyInitialWindowSize) { - this.spdyInitialWindowSize = spdyInitialWindowSize; - return this; - } - - /** - * Configures the maximum number of concurrent streams over a single - * SPDY session. - * - * @param spdyMaxConcurrentStreams the maximum number of concurrent - * streams over a single SPDY session. - * - * @return this - * - * @since 2.0 - */ - public Builder setSpdyMaxConcurrentStreams(int spdyMaxConcurrentStreams) { - this.spdyMaxConcurrentStreams = spdyMaxConcurrentStreams; - return this; - } - - public Builder setTimeConverter(TimeConverter timeConverter) { - this.timeConverter = timeConverter; - return this; - } - - public Builder setAcceptAnyCertificate(boolean acceptAnyCertificate) { - this.acceptAnyCertificate = acceptAnyCertificate; - return this; - } - - /** - * Create a config builder with values taken from the given prototype configuration. - * - * @param prototype the configuration to use as a prototype. - */ - public Builder(AsyncHttpClientConfig prototype) { - allowPoolingConnections = prototype.isAllowPoolingConnections(); - providerConfig = prototype.getAsyncHttpProviderConfig(); - connectionTimeout = prototype.getConnectionTimeout(); - pooledConnectionIdleTimeout = prototype.getPooledConnectionIdleTimeout(); - readTimeout = prototype.getReadTimeout(); - maxConnectionsPerHost = prototype.getMaxConnectionsPerHost(); - connectionTTL = prototype.getConnectionTTL(); - maxRedirects = prototype.getMaxRedirects(); - maxConnections = prototype.getMaxConnections(); - proxyServerSelector = prototype.getProxyServerSelector(); - realm = prototype.getRealm(); - requestTimeout = prototype.getRequestTimeout(); - sslContext = prototype.getSSLContext(); - userAgent = prototype.getUserAgent(); - followRedirect = prototype.isFollowRedirect(); - compressionEnabled = prototype.isCompressionEnabled(); - applicationThreadPool = prototype.executorService(); - - requestFilters.clear(); - responseFilters.clear(); - ioExceptionFilters.clear(); - - requestFilters.addAll(prototype.getRequestFilters()); - responseFilters.addAll(prototype.getResponseFilters()); - ioExceptionFilters.addAll(prototype.getIOExceptionFilters()); - - disableUrlEncodingForBoundRequests = prototype.isDisableUrlEncodingForBoundRequests(); - ioThreadMultiplier = prototype.getIoThreadMultiplier(); - maxRequestRetry = prototype.getMaxRequestRetry(); - allowPoolingSslConnections = prototype.isAllowPoolingConnections(); - removeQueryParamOnRedirect = prototype.isRemoveQueryParamOnRedirect(); - hostnameVerifier = prototype.getHostnameVerifier(); - strict302Handling = prototype.isStrict302Handling(); - timeConverter = prototype.timeConverter; - acceptAnyCertificate = prototype.acceptAnyCertificate; - - spdyEnabled = prototype.isSpdyEnabled(); - spdyInitialWindowSize = prototype.getSpdyInitialWindowSize(); - spdyMaxConcurrentStreams = prototype.getSpdyMaxConcurrentStreams(); - } - - /** - * Build an {@link AsyncHttpClientConfig} - * - * @return an {@link AsyncHttpClientConfig} - */ - public AsyncHttpClientConfig build() { - - if (proxyServerSelector == null && useProxySelector) - proxyServerSelector = ProxyUtils.getJdkDefaultProxyServerSelector(); - - if (proxyServerSelector == null && useProxyProperties) - proxyServerSelector = ProxyUtils.createProxyServerSelector(System.getProperties()); - - if (proxyServerSelector == null) - proxyServerSelector = ProxyServerSelector.NO_PROXY_SELECTOR; - - if (acceptAnyCertificate) - hostnameVerifier = null; - else if (hostnameVerifier == null) - hostnameVerifier = new DefaultHostnameVerifier(); - - return new AsyncHttpClientConfig(connectionTimeout,// - maxConnections,// - maxConnectionsPerHost,// - requestTimeout,// - readTimeout,// - webSocketTimeout,// - allowPoolingConnections,// - allowPoolingSslConnections,// - pooledConnectionIdleTimeout,// - connectionTTL,// - sslContext, // - hostnameVerifier,// - acceptAnyCertificate, // - followRedirect, // - maxRedirects, // - removeQueryParamOnRedirect,// - strict302Handling, // - applicationThreadPool, // - proxyServerSelector, // - useRelativeURIsWithConnectProxies, // - compressionEnabled, // - userAgent,// - realm,// - requestFilters, // - responseFilters,// - ioExceptionFilters,// - maxRequestRetry, // - disableUrlEncodingForBoundRequests, // - ioThreadMultiplier, // - timeConverter,// - providerConfig, // - spdyEnabled, // - spdyInitialWindowSize, // - spdyMaxConcurrentStreams); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java deleted file mode 100644 index 0bb496f28e..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java +++ /dev/null @@ -1,238 +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; - -import static org.asynchttpclient.AsyncHttpClientConfigDefaults.*; - -import org.asynchttpclient.filter.IOExceptionFilter; -import org.asynchttpclient.filter.RequestFilter; -import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.util.ProxyUtils; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLContext; - -import java.util.LinkedList; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; - -/** - * Simple JavaBean version of {@link AsyncHttpClientConfig} - */ -public class AsyncHttpClientConfigBean extends AsyncHttpClientConfig { - - public AsyncHttpClientConfigBean() { - configureExecutors(); - configureDefaults(); - configureFilters(); - } - - void configureFilters() { - requestFilters = new LinkedList(); - responseFilters = new LinkedList(); - ioExceptionFilters = new LinkedList(); - } - - void configureDefaults() { - maxConnections = defaultMaxConnections(); - maxConnectionsPerHost = defaultMaxConnectionsPerHost(); - connectionTimeout = defaultConnectionTimeout(); - webSocketTimeout = defaultWebSocketTimeout(); - pooledConnectionIdleTimeout = defaultPooledConnectionIdleTimeout(); - readTimeout = defaultReadTimeout(); - requestTimeout = defaultRequestTimeout(); - connectionTTL = defaultConnectionTTL(); - followRedirect = defaultFollowRedirect(); - maxRedirects = defaultMaxRedirects(); - compressionEnabled = defaultCompressionEnabled(); - userAgent = defaultUserAgent(); - allowPoolingConnections = defaultAllowPoolingConnections(); - useRelativeURIsWithConnectProxies = defaultUseRelativeURIsWithConnectProxies(); - maxRequestRetry = defaultMaxRequestRetry(); - ioThreadMultiplier = defaultIoThreadMultiplier(); - allowPoolingSslConnections = defaultAllowPoolingSslConnections(); - disableUrlEncodingForBoundRequests = defaultDisableUrlEncodingForBoundRequests(); - removeQueryParamOnRedirect = defaultRemoveQueryParamOnRedirect(); - strict302Handling = defaultStrict302Handling(); - acceptAnyCertificate = defaultAcceptAnyCertificate(); - - if (defaultUseProxySelector()) { - proxyServerSelector = ProxyUtils.getJdkDefaultProxyServerSelector(); - } else if (defaultUseProxyProperties()) { - proxyServerSelector = ProxyUtils.createProxyServerSelector(System.getProperties()); - } - // AHC 2 - spdyEnabled = defaultSpdyEnabled(); - spdyInitialWindowSize = defaultSpdyInitialWindowSize(); - spdyMaxConcurrentStreams = defaultSpdyMaxConcurrentStreams(); - } - - void configureExecutors() { - applicationThreadPool = Executors.newCachedThreadPool(new ThreadFactory() { - public Thread newThread(Runnable r) { - Thread t = new Thread(r, "AsyncHttpClient-Callback"); - t.setDaemon(true); - return t; - } - }); - } - - public AsyncHttpClientConfigBean setMaxTotalConnections(int maxConnections) { - this.maxConnections = maxConnections; - return this; - } - - public AsyncHttpClientConfigBean setMaxConnectionsPerHost(int maxConnectionsPerHost) { - this.maxConnectionsPerHost = maxConnectionsPerHost; - return this; - } - - public AsyncHttpClientConfigBean setConnectionTimeout(int connectionTimeout) { - this.connectionTimeout = connectionTimeout; - return this; - } - - public AsyncHttpClientConfigBean setConnectionTTL(int connectionTTL) { - this.connectionTTL = connectionTTL; - return this; - } - - public AsyncHttpClientConfigBean setPooledConnectionIdleTimeout(int pooledConnectionIdleTimeout) { - this.pooledConnectionIdleTimeout = pooledConnectionIdleTimeout; - return this; - } - - public AsyncHttpClientConfigBean setReadTimeout(int readTimeout) { - this.readTimeout = readTimeout; - return this; - } - - public AsyncHttpClientConfigBean setRequestTimeout(int requestTimeout) { - this.requestTimeout = requestTimeout; - return this; - } - - public AsyncHttpClientConfigBean setFollowRedirect(boolean followRedirect) { - this.followRedirect = followRedirect; - return this; - } - - public AsyncHttpClientConfigBean setMaxRedirects(int maxRedirects) { - this.maxRedirects = maxRedirects; - return this; - } - - public AsyncHttpClientConfigBean setStrict302Handling(boolean strict302Handling) { - this.strict302Handling = strict302Handling; - return this; - } - - public AsyncHttpClientConfigBean setCompressionEnabled(boolean compressionEnabled) { - this.compressionEnabled = compressionEnabled; - return this; - } - - public AsyncHttpClientConfigBean setUserAgent(String userAgent) { - this.userAgent = userAgent; - return this; - } - - public AsyncHttpClientConfigBean setAllowPoolingConnections(boolean allowPoolingConnections) { - this.allowPoolingConnections = allowPoolingConnections; - return this; - } - - public AsyncHttpClientConfigBean setApplicationThreadPool(ExecutorService applicationThreadPool) { - if (this.applicationThreadPool != null) { - this.applicationThreadPool.shutdownNow(); - } - this.applicationThreadPool = applicationThreadPool; - return this; - } - - public AsyncHttpClientConfigBean setProxyServer(ProxyServer proxyServer) { - this.proxyServerSelector = ProxyUtils.createProxyServerSelector(proxyServer); - return this; - } - - public AsyncHttpClientConfigBean setProxyServerSelector(ProxyServerSelector proxyServerSelector) { - this.proxyServerSelector = proxyServerSelector; - return this; - } - - public AsyncHttpClientConfigBean setSslContext(SSLContext sslContext) { - this.sslContext = sslContext; - return this; - } - - public AsyncHttpClientConfigBean setProviderConfig(AsyncHttpProviderConfig providerConfig) { - this.providerConfig = providerConfig; - return this; - } - - public AsyncHttpClientConfigBean setRealm(Realm realm) { - this.realm = realm; - return this; - } - - public AsyncHttpClientConfigBean addRequestFilter(RequestFilter requestFilter) { - requestFilters.add(requestFilter); - return this; - } - - public AsyncHttpClientConfigBean addResponseFilters(ResponseFilter responseFilter) { - responseFilters.add(responseFilter); - return this; - } - - public AsyncHttpClientConfigBean addIoExceptionFilters(IOExceptionFilter ioExceptionFilter) { - ioExceptionFilters.add(ioExceptionFilter); - return this; - } - - public AsyncHttpClientConfigBean setMaxRequestRetry(int maxRequestRetry) { - this.maxRequestRetry = maxRequestRetry; - return this; - } - - public AsyncHttpClientConfigBean setAllowPoolingSslConnections(boolean allowPoolingSslConnections) { - this.allowPoolingSslConnections = allowPoolingSslConnections; - return this; - } - - public AsyncHttpClientConfigBean setDisableUrlEncodingForBoundRequests(boolean disableUrlEncodingForBoundRequests) { - this.disableUrlEncodingForBoundRequests = disableUrlEncodingForBoundRequests; - return this; - } - - public AsyncHttpClientConfigBean setRemoveQueryParamOnRedirect(boolean removeQueryParamOnRedirect) { - this.removeQueryParamOnRedirect = removeQueryParamOnRedirect; - return this; - } - - public AsyncHttpClientConfigBean setHostnameVerifier(HostnameVerifier hostnameVerifier) { - this.hostnameVerifier = hostnameVerifier; - return this; - } - - public AsyncHttpClientConfigBean setIoThreadMultiplier(int ioThreadMultiplier) { - this.ioThreadMultiplier = ioThreadMultiplier; - return this; - } - - public AsyncHttpClientConfigBean setAcceptAnyCertificate(boolean acceptAnyCertificate) { - this.acceptAnyCertificate = acceptAnyCertificate; - return this; - } -} diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java deleted file mode 100644 index 6733cd5c10..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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; - -import static org.asynchttpclient.util.MiscUtils.getBoolean; - -public final class AsyncHttpClientConfigDefaults { - - private AsyncHttpClientConfigDefaults() { - } - - public static final String ASYNC_CLIENT = AsyncHttpClientConfig.class.getName() + "."; - - public static int defaultMaxConnections() { - return Integer.getInteger(ASYNC_CLIENT + "maxConnections", -1); - } - - public static int defaultMaxConnectionsPerHost() { - return Integer.getInteger(ASYNC_CLIENT + "maxConnectionsPerHost", -1); - } - - public static int defaultConnectionTimeout() { - return Integer.getInteger(ASYNC_CLIENT + "connectionTimeout", 60 * 1000); - } - - public static int defaultPooledConnectionIdleTimeout() { - return Integer.getInteger(ASYNC_CLIENT + "pooledConnectionIdleTimeout", 60 * 1000); - } - - public static int defaultReadTimeout() { - return Integer.getInteger(ASYNC_CLIENT + "readTimeout", 60 * 1000); - } - - public static int defaultRequestTimeout() { - return Integer.getInteger(ASYNC_CLIENT + "requestTimeout", 60 * 1000); - } - - public static int defaultWebSocketTimeout() { - return Integer.getInteger(ASYNC_CLIENT + "webSocketTimeout", 15 * 60 * 1000); - } - - public static int defaultConnectionTTL() { - return Integer.getInteger(ASYNC_CLIENT + "connectionTTL", -1); - } - - public static boolean defaultFollowRedirect() { - return Boolean.getBoolean(ASYNC_CLIENT + "followRedirect"); - } - - public static int defaultMaxRedirects() { - return Integer.getInteger(ASYNC_CLIENT + "maxRedirects", 5); - } - - public static boolean defaultCompressionEnabled() { - return Boolean.getBoolean(ASYNC_CLIENT + "compressionEnabled"); - } - - public static String defaultUserAgent() { - return System.getProperty(ASYNC_CLIENT + "userAgent", "AHC/2.0"); - } - - public static int defaultIoThreadMultiplier() { - return Integer.getInteger(ASYNC_CLIENT + "ioThreadMultiplier", 2); - } - - public static boolean defaultUseProxySelector() { - return Boolean.getBoolean(ASYNC_CLIENT + "useProxySelector"); - } - - public static boolean defaultUseProxyProperties() { - return Boolean.getBoolean(ASYNC_CLIENT + "useProxyProperties"); - } - - public static boolean defaultStrict302Handling() { - return Boolean.getBoolean(ASYNC_CLIENT + "strict302Handling"); - } - - public static boolean defaultAllowPoolingConnections() { - return getBoolean(ASYNC_CLIENT + "allowPoolingConnections", true); - } - - public static boolean defaultUseRelativeURIsWithConnectProxies() { - return getBoolean(ASYNC_CLIENT + "useRelativeURIsWithConnectProxies", true); - } - - public static int defaultMaxRequestRetry() { - return Integer.getInteger(ASYNC_CLIENT + "maxRequestRetry", 5); - } - - public static boolean defaultAllowPoolingSslConnections() { - return getBoolean(ASYNC_CLIENT + "allowPoolingSslConnections", true); - } - - public static boolean defaultDisableUrlEncodingForBoundRequests() { - return Boolean.getBoolean(ASYNC_CLIENT + "disableUrlEncodingForBoundRequests"); - } - - public static boolean defaultRemoveQueryParamOnRedirect() { - return getBoolean(ASYNC_CLIENT + "removeQueryParamOnRedirect", true); - } - - public static boolean defaultSpdyEnabled() { - return Boolean.getBoolean(ASYNC_CLIENT + "spdyEnabled"); - } - - public static int defaultSpdyInitialWindowSize() { - return Integer.getInteger(ASYNC_CLIENT + "spdyInitialWindowSize", 10 * 1024 * 1024); - } - - public static int defaultSpdyMaxConcurrentStreams() { - return Integer.getInteger(ASYNC_CLIENT + "spdyMaxConcurrentStreams", 100); - } - - public static boolean defaultAcceptAnyCertificate() { - return getBoolean(ASYNC_CLIENT + "acceptAnyCertificate", false); - } -} diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpProvider.java b/api/src/main/java/org/asynchttpclient/AsyncHttpProvider.java deleted file mode 100644 index 848249a6d1..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 java.io.Closeable; -import java.io.IOException; - -/** - * Interface to be used when implementing custom asynchronous I/O HTTP client. - */ -public interface AsyncHttpProvider extends Closeable { - - /** - * Execute the request and invoke the {@link AsyncHandler} when the response arrive. - * - * @param handler an instance of {@link AsyncHandler} - * @return a {@link ListenableFuture} of Type T. - * @throws IOException - */ - ListenableFuture execute(Request request, AsyncHandler handler) throws IOException; - - /** - * Close the current underlying TCP/HTTP connection. - */ - void close(); -} diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpProviderConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpProviderConfig.java deleted file mode 100644 index f511580c9b..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpProviderConfig.java +++ /dev/null @@ -1,56 +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; - -import java.util.Map; -import java.util.Set; - -/** - * {@link AsyncHttpProvider} proprietary configurable properties. Note that properties are - * AsyncHttpProvider dependent, so make sure you consult the AsyncHttpProvider's documentation - * about what is supported and what's not. - */ -public interface AsyncHttpProviderConfig { - - /** - * Add a property that will be used when the AsyncHttpClient initialize its {@link AsyncHttpProvider} - * - * @param name the name of the property - * @param value the value of the property - * @return this instance of AsyncHttpProviderConfig - */ - AsyncHttpProviderConfig addProperty(U name, V value); - - /** - * Return the value associated with the property's name - * - * @param name - * @return this instance of AsyncHttpProviderConfig - */ - V getProperty(U name); - - /** - * Remove the value associated with the property's name - * - * @param name - * @return true if removed - */ - V removeProperty(U name); - - /** - * Return the curent entry set. - * - * @return a the curent entry set. - */ - Set> propertiesSet(); -} diff --git a/api/src/main/java/org/asynchttpclient/Body.java b/api/src/main/java/org/asynchttpclient/Body.java deleted file mode 100644 index 422e365e2f..0000000000 --- a/api/src/main/java/org/asynchttpclient/Body.java +++ /dev/null @@ -1,41 +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; - -import java.io.Closeable; -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * A request body. - */ -public interface Body extends Closeable { - - /** - * Gets the length of the body. - * - * @return The length of the body in bytes, or negative if unknown. - */ - long getContentLength(); - - /** - * Reads the next chunk of bytes from the body. - * - * @param buffer The buffer to store the chunk in, must not be {@code null}. - * @return The non-negative number of bytes actually read or {@code -1} if the body has been read completely. - * @throws IOException If the chunk could not be read. - */ - // FIXME introduce a visitor pattern so that Netty can pass a pooled buffer - long read(ByteBuffer buffer) throws IOException; -} diff --git a/api/src/main/java/org/asynchttpclient/BodyConsumer.java b/api/src/main/java/org/asynchttpclient/BodyConsumer.java deleted file mode 100644 index 190c586ae7..0000000000 --- a/api/src/main/java/org/asynchttpclient/BodyConsumer.java +++ /dev/null @@ -1,32 +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; - -import java.io.Closeable; -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * A simple API to be used with the {@link SimpleAsyncHttpClient} class in order to process response's bytes. - */ -public interface BodyConsumer extends Closeable { - - /** - * Consume the received bytes. - * - * @param byteBuffer a {@link ByteBuffer} representation of the response's chunk. - * @throws IOException - */ - void consume(ByteBuffer byteBuffer) throws IOException; -} diff --git a/api/src/main/java/org/asynchttpclient/BodyDeferringAsyncHandler.java b/api/src/main/java/org/asynchttpclient/BodyDeferringAsyncHandler.java deleted file mode 100644 index 4ab5b30beb..0000000000 --- a/api/src/main/java/org/asynchttpclient/BodyDeferringAsyncHandler.java +++ /dev/null @@ -1,277 +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; - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.Semaphore; - -/** - * An AsyncHandler that returns Response (without body, so status code and - * headers only) as fast as possible for inspection, but leaves you the option - * to defer body consumption. - *

- * This class introduces new call: getResponse(), that blocks caller thread as - * 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 - * client.prepareGet("http://foo.com/aResource").execute().get() - * 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 - * 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. - *

- * All these above means that this AsyncHandler needs a bit of different - * handling than "recommended" way. Some examples: - *

- *

- *     FileOutputStream fos = ...
- *     BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(fos);
- *     // client executes async
- *     Future<Response> fr = client.prepareGet("http://foo.com/aresource").execute(
- * 	bdah);
- *     // main thread will block here until headers are available
- *     Response response = bdah.getResponse();
- *     // you can continue examine headers while actual body download happens
- *     // in separate thread
- *     // ...
- *     // finally "join" the download
- *     fr.get();
- * 
- *

- *

- *     PipedOutputStream pout = new PipedOutputStream();
- *     BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(pout);
- *     // client executes async
- *     Future<Response> fr = client.prepareGet("http://foo.com/aresource").execute(bdah);
- *     // main thread will block here until headers are available
- *     Response response = bdah.getResponse();
- *     if (response.getStatusCode() == 200) {
- *      InputStream pin = new BodyDeferringInputStream(fr,new PipedInputStream(pout));
- *      // consume InputStream
- *      ...
- *     } else {
- *      // handle unexpected response status code
- *      ...
- *     }
- * 
- */ -public class BodyDeferringAsyncHandler implements AsyncHandler { - private final Response.ResponseBuilder responseBuilder = new Response.ResponseBuilder(); - - private final CountDownLatch headersArrived = new CountDownLatch(1); - - private final OutputStream output; - - private boolean responseSet; - - private volatile Response response; - - private volatile Throwable throwable; - - private final Semaphore semaphore = new Semaphore(1); - - public BodyDeferringAsyncHandler(final OutputStream os) { - this.output = os; - this.responseSet = false; - } - - public void onThrowable(Throwable t) { - this.throwable = t; - // Counting down to handle error cases too. - // In "premature exceptions" cases, the onBodyPartReceived() and - // onCompleted() - // methods will never be invoked, leaving caller of getResponse() method - // blocked forever. - try { - semaphore.acquire(); - } catch (InterruptedException e) { - // Ignore - } finally { - headersArrived.countDown(); - semaphore.release(); - } - - try { - closeOut(); - } catch (IOException e) { - // ignore - } - } - - public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { - responseBuilder.reset(); - responseBuilder.accumulate(responseStatus); - return STATE.CONTINUE; - } - - public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { - responseBuilder.accumulate(headers); - return STATE.CONTINUE; - } - - public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - // body arrived, flush headers - if (!responseSet) { - response = responseBuilder.build(); - responseSet = true; - headersArrived.countDown(); - } - - bodyPart.writeTo(output); - return STATE.CONTINUE; - } - - protected void closeOut() throws IOException { - try { - output.flush(); - } finally { - output.close(); - } - } - - public Response onCompleted() throws IOException { - - if (!responseSet) { - response = responseBuilder.build(); - responseSet = true; - } - - // Counting down to handle error cases too. - // In "normal" cases, latch is already at 0 here - // But in other cases, for example when because of some error - // onBodyPartReceived() is never called, the caller - // of getResponse() would remain blocked infinitely. - // By contract, onCompleted() is always invoked, even in case of errors - headersArrived.countDown(); - - closeOut(); - - try { - semaphore.acquire(); - if (throwable != null) { - IOException ioe = new IOException(throwable.getMessage()); - ioe.initCause(throwable); - throw ioe; - } else { - // sending out current response - return responseBuilder.build(); - } - } catch (InterruptedException e) { - return null; - } finally { - semaphore.release(); - } - } - - /** - * This method -- unlike Future.get() -- will block only as long, - * as headers arrive. This is useful for large transfers, to examine headers - * ASAP, and defer body streaming to it's fine destination and prevent - * unneeded bandwidth consumption. The response here will contain the very - * 1st response from server, so status code and headers, but it might be - * incomplete in case of broken servers sending trailing headers. In that - * case, the "usual" Future.get() method will return complete - * headers, but multiple invocations of getResponse() will always return the - * 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 null - * in case of some errors. - * - * @return a {@link Response} - * @throws InterruptedException - */ - public Response getResponse() throws InterruptedException, IOException { - // block here as long as headers arrive - headersArrived.await(); - - try { - semaphore.acquire(); - if (throwable != null) { - IOException ioe = new IOException(throwable.getMessage()); - ioe.initCause(throwable); - throw ioe; - } else { - return response; - } - } finally { - semaphore.release(); - } - } - - // == - - /** - * A simple helper class that is used to perform automatic "join" for async - * download and the error checking of the Future of the request. - */ - public static class BodyDeferringInputStream extends FilterInputStream { - private final Future future; - - private final BodyDeferringAsyncHandler bdah; - - public BodyDeferringInputStream(final Future future, final BodyDeferringAsyncHandler bdah, final InputStream in) { - super(in); - this.future = future; - this.bdah = bdah; - } - - /** - * Closes the input stream, and "joins" (wait for complete execution - * together with potential exception thrown) of the async request. - */ - public void close() throws IOException { - // close - super.close(); - // "join" async request - try { - getLastResponse(); - } catch (Exception e) { - IOException ioe = new IOException(e.getMessage()); - ioe.initCause(e); - throw ioe; - } - } - - /** - * Delegates to {@link BodyDeferringAsyncHandler#getResponse()}. Will - * blocks as long as headers arrives only. Might return - * null. See - * {@link BodyDeferringAsyncHandler#getResponse()} method for details. - * - * @return a {@link Response} - * @throws InterruptedException - */ - public Response getAsapResponse() throws InterruptedException, IOException { - return bdah.getResponse(); - } - - /** - * Delegates to Future#get() method. Will block - * as long as complete response arrives. - * - * @return a {@link Response} - * @throws InterruptedException - * @throws ExecutionException - */ - public Response getLastResponse() throws InterruptedException, ExecutionException { - return future.get(); - } - } -} \ No newline at end of file diff --git a/api/src/main/java/org/asynchttpclient/BoundRequestBuilder.java b/api/src/main/java/org/asynchttpclient/BoundRequestBuilder.java deleted file mode 100644 index 638450a202..0000000000 --- a/api/src/main/java/org/asynchttpclient/BoundRequestBuilder.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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; - -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.multipart.Part; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -public class BoundRequestBuilder extends RequestBuilderBase { - - private final AsyncHttpClient client; - - public BoundRequestBuilder(AsyncHttpClient client, String method, boolean isDisableUrlEncoding) { - super(BoundRequestBuilder.class, method, isDisableUrlEncoding); - this.client = client; - } - - public BoundRequestBuilder(AsyncHttpClient client, Request prototype) { - super(BoundRequestBuilder.class, prototype); - this.client = client; - } - - public ListenableFuture execute(AsyncHandler handler) throws IOException { - return client.executeRequest(build(), handler); - } - - public ListenableFuture execute() throws IOException { - return client.executeRequest(build(), new AsyncCompletionHandlerBase()); - } - - // Note: For now we keep the delegates in place even though they are not - // needed - // since otherwise Clojure (and maybe other languages) won't be able to - // access these methods - see Clojure tickets 126 and 259 - - @Override - public BoundRequestBuilder addBodyPart(Part part) { - return super.addBodyPart(part); - } - - @Override - public BoundRequestBuilder addCookie(Cookie cookie) { - return super.addCookie(cookie); - } - - @Override - public BoundRequestBuilder addHeader(String name, String value) { - return super.addHeader(name, value); - } - - @Override - public BoundRequestBuilder addFormParam(String key, String value) { - return super.addFormParam(key, value); - } - - @Override - public BoundRequestBuilder addQueryParam(String name, String value) { - return super.addQueryParam(name, value); - } - - @Override - public Request build() { - return super.build(); - } - - @Override - public BoundRequestBuilder setBody(byte[] data) { - return super.setBody(data); - } - - @Override - public BoundRequestBuilder setBody(InputStream stream) { - return super.setBody(stream); - } - - @Override - public BoundRequestBuilder setBody(String data) { - return super.setBody(data); - } - - @Override - public BoundRequestBuilder setHeader(String name, String value) { - return super.setHeader(name, value); - } - - @Override - public BoundRequestBuilder setHeaders(FluentCaseInsensitiveStringsMap headers) { - return super.setHeaders(headers); - } - - @Override - public BoundRequestBuilder setHeaders(Map> headers) { - return super.setHeaders(headers); - } - - @Override - public BoundRequestBuilder setFormParams(Map> params) { - return super.setFormParams(params); - } - - @Override - public BoundRequestBuilder setFormParams(List params) { - return super.setFormParams(params); - } - - @Override - public BoundRequestBuilder setUrl(String url) { - return super.setUrl(url); - } - - @Override - public BoundRequestBuilder setVirtualHost(String virtualHost) { - return super.setVirtualHost(virtualHost); - } - - public BoundRequestBuilder setSignatureCalculator(SignatureCalculator signatureCalculator) { - return super.setSignatureCalculator(signatureCalculator); - } -} diff --git a/api/src/main/java/org/asynchttpclient/ConnectionPoolKeyStrategy.java b/api/src/main/java/org/asynchttpclient/ConnectionPoolKeyStrategy.java deleted file mode 100644 index 704ed78cb1..0000000000 --- a/api/src/main/java/org/asynchttpclient/ConnectionPoolKeyStrategy.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 org.asynchttpclient.uri.UriComponents; - -public interface ConnectionPoolKeyStrategy { - - String getKey(UriComponents uri, ProxyServer proxy); -} diff --git a/api/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java deleted file mode 100644 index 63536a37c4..0000000000 --- a/api/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.RequestFilter; -import org.asynchttpclient.resumable.ResumableAsyncHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; - -public class DefaultAsyncHttpClient implements AsyncHttpClient { - - /** - * Providers that will be searched for, on the classpath, in order when no - * provider is explicitly specified by the developer. - */ - private static final String[] DEFAULT_PROVIDERS = {// - "org.asynchttpclient.providers.netty.NettyAsyncHttpProvider",/**/ - "org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider"// - }; - - private final AsyncHttpProvider httpProvider; - private final AsyncHttpClientConfig config; - private final static Logger logger = LoggerFactory.getLogger(DefaultAsyncHttpClient.class); - private final AtomicBoolean isClosed = new AtomicBoolean(false); - - /** - * Default signature calculator to use for all requests constructed by this client instance. - * - * @since 1.1 - */ - protected SignatureCalculator signatureCalculator; - - /** - * Create a new HTTP Asynchronous Client using the default {@link AsyncHttpClientConfig} configuration. The - * default {@link AsyncHttpProvider} that will be used will be based on the classpath configuration. - * - * The default providers will be searched for in this order: - *
    - *
  • netty
  • - *
  • grizzly
  • - *
- * - * If none of those providers are found, then the engine will throw an IllegalStateException. - */ - public DefaultAsyncHttpClient() { - this(new AsyncHttpClientConfig.Builder().build()); - } - - /** - * Create a new HTTP Asynchronous Client using an implementation of {@link AsyncHttpProvider} and - * the default {@link AsyncHttpClientConfig} configuration. - * - * @param provider a {@link AsyncHttpProvider} - */ - public DefaultAsyncHttpClient(AsyncHttpProvider provider) { - this(provider, new AsyncHttpClientConfig.Builder().build()); - } - - /** - * Create a new HTTP Asynchronous Client using the specified {@link AsyncHttpClientConfig} configuration. - * This configuration will be passed to the default {@link AsyncHttpProvider} that will be selected based on - * the classpath configuration. - * - * The default providers will be searched for in this order: - *
    - *
  • netty
  • - *
  • grizzly
  • - *
- * - * If none of those providers are found, then the engine will throw an IllegalStateException. - * - * @param config a {@link AsyncHttpClientConfig} - */ - public DefaultAsyncHttpClient(AsyncHttpClientConfig config) { - this(loadDefaultProvider(DEFAULT_PROVIDERS, config), config); - } - - /** - * Create a new HTTP Asynchronous Client using a {@link AsyncHttpClientConfig} configuration and - * and a AsyncHttpProvider class' name. - * - * @param config a {@link AsyncHttpClientConfig} - * @param providerClass a {@link AsyncHttpProvider} - */ - public DefaultAsyncHttpClient(String providerClass, AsyncHttpClientConfig config) { - this(loadProvider(providerClass, config), config); - } - - /** - * Create a new HTTP Asynchronous Client using a {@link AsyncHttpClientConfig} configuration and - * and a {@link AsyncHttpProvider}. - * - * @param config a {@link AsyncHttpClientConfig} - * @param httpProvider a {@link AsyncHttpProvider} - */ - public DefaultAsyncHttpClient(AsyncHttpProvider httpProvider, AsyncHttpClientConfig config) { - this.config = config; - this.httpProvider = httpProvider; - } - - @Override - public AsyncHttpProvider getProvider() { - return httpProvider; - } - - @Override - public void close() { - if (isClosed.compareAndSet(false, true)) - httpProvider.close(); - } - - @Override - public void closeAsynchronously() { - final ExecutorService e = Executors.newSingleThreadExecutor(); - e.submit(new Runnable() { - public void run() { - try { - close(); - } catch (Throwable t) { - logger.warn("", t); - } finally { - e.shutdown(); - } - } - }); - } - - @Override - protected void finalize() throws Throwable { - try { - if (!isClosed.get()) { - logger.error("AsyncHttpClient.close() hasn't been invoked, which may produce file descriptor leaks"); - } - } finally { - super.finalize(); - } - } - - @Override - public boolean isClosed() { - return isClosed.get(); - } - - @Override - public AsyncHttpClientConfig getConfig() { - return config; - } - - @Override - public DefaultAsyncHttpClient setSignatureCalculator(SignatureCalculator signatureCalculator) { - this.signatureCalculator = signatureCalculator; - return this; - } - - @Override - public BoundRequestBuilder prepareGet(String url) { - return requestBuilder("GET", url); - } - - @Override - public BoundRequestBuilder prepareConnect(String url) { - return requestBuilder("CONNECT", url); - } - - @Override - public BoundRequestBuilder prepareOptions(String url) { - return requestBuilder("OPTIONS", url); - } - - @Override - public BoundRequestBuilder prepareHead(String url) { - return requestBuilder("HEAD", url); - } - - @Override - public BoundRequestBuilder preparePost(String url) { - return requestBuilder("POST", url); - } - - @Override - public BoundRequestBuilder preparePut(String url) { - return requestBuilder("PUT", url); - } - - @Override - public BoundRequestBuilder prepareDelete(String url) { - return requestBuilder("DELETE", url); - } - - @Override - public BoundRequestBuilder preparePatch(String url) { - return requestBuilder("PATCH", url); - } - - @Override - public BoundRequestBuilder prepareTrace(String url) { - return requestBuilder("TRACE", url); - } - - @Override - public BoundRequestBuilder prepareRequest(Request request) { - return requestBuilder(request); - } - - @Override - public ListenableFuture executeRequest(Request request, AsyncHandler handler) throws IOException { - - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(handler).request(request).build(); - fc = preProcessRequest(fc); - - return httpProvider.execute(fc.getRequest(), fc.getAsyncHandler()); - } - - @Override - public ListenableFuture executeRequest(Request request) throws IOException { - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(new AsyncCompletionHandlerBase()) - .request(request).build(); - fc = preProcessRequest(fc); - return httpProvider.execute(fc.getRequest(), fc.getAsyncHandler()); - } - - /** - * Configure and execute the associated {@link RequestFilter}. This class may decorate the {@link Request} and {@link AsyncHandler} - * - * @param fc {@link FilterContext} - * @return {@link FilterContext} - */ - private FilterContext preProcessRequest(FilterContext fc) throws IOException { - for (RequestFilter asyncFilter : config.getRequestFilters()) { - try { - fc = asyncFilter.filter(fc); - if (fc == null) { - throw new NullPointerException("FilterContext is null"); - } - } catch (FilterException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } - } - - Request request = fc.getRequest(); - if (fc.getAsyncHandler() instanceof ResumableAsyncHandler) { - request = ResumableAsyncHandler.class.cast(fc.getAsyncHandler()).adjustRequestRange(request); - } - - if (request.getRangeOffset() != 0) { - RequestBuilder builder = new RequestBuilder(request); - builder.setHeader("Range", "bytes=" + request.getRangeOffset() + "-"); - request = builder.build(); - } - fc = new FilterContext.FilterContextBuilder(fc).request(request).build(); - return fc; - } - - @SuppressWarnings("unchecked") - private static AsyncHttpProvider loadProvider(final String className, final AsyncHttpClientConfig config) { - try { - Class providerClass = (Class) Thread.currentThread().getContextClassLoader() - .loadClass(className); - return providerClass.getDeclaredConstructor(new Class[] { AsyncHttpClientConfig.class }).newInstance(config); - } catch (Throwable t) { - if (t instanceof InvocationTargetException) { - final InvocationTargetException ite = (InvocationTargetException) t; - if (logger.isErrorEnabled()) { - logger.error("Unable to instantiate provider {}. Trying other providers.", className); - logger.error(ite.getCause().toString(), ite.getCause()); - } - } - // Let's try with another classloader - try { - Class providerClass = (Class) DefaultAsyncHttpClient.class.getClassLoader().loadClass( - className); - return providerClass.getDeclaredConstructor(new Class[] { AsyncHttpClientConfig.class }).newInstance(config); - } catch (Throwable ignored) { - } - } - return null; - } - - private static AsyncHttpProvider loadDefaultProvider(String[] providerClassNames, AsyncHttpClientConfig config) { - AsyncHttpProvider provider; - for (final String className : providerClassNames) { - provider = loadProvider(className, config); - if (provider != null) { - return provider; - } - } - throw new IllegalStateException("No providers found on the classpath"); - } - - protected BoundRequestBuilder requestBuilder(String method, String url) { - return new BoundRequestBuilder(this, method, config.isDisableUrlEncodingForBoundRequests()).setUrl(url).setSignatureCalculator(signatureCalculator); - } - - protected BoundRequestBuilder requestBuilder(Request prototype) { - return new BoundRequestBuilder(this, prototype).setSignatureCalculator(signatureCalculator); - } -} diff --git a/api/src/main/java/org/asynchttpclient/DefaultConnectionPoolStrategy.java b/api/src/main/java/org/asynchttpclient/DefaultConnectionPoolStrategy.java deleted file mode 100644 index 6b2c9ae5b7..0000000000 --- a/api/src/main/java/org/asynchttpclient/DefaultConnectionPoolStrategy.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.AsyncHttpProviderUtils; - -public enum DefaultConnectionPoolStrategy implements ConnectionPoolKeyStrategy { - - INSTANCE; - - @Override - public String getKey(UriComponents uri, ProxyServer proxyServer) { - String serverPart = AsyncHttpProviderUtils.getBaseUrl(uri); - return proxyServer != null ? proxyServer.getUrl() + serverPart : serverPart; - } -} diff --git a/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java b/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java deleted file mode 100644 index 1cbe5baa58..0000000000 --- a/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java +++ /dev/null @@ -1,529 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -/** - * An implementation of a {@code String -> List} map that adds a fluent interface, i.e. methods that - * return this instance. This class differs from {@link FluentStringsMap} in that keys are treated in an - * case-insensitive matter, i.e. case of the key doesn't matter when retrieving values or changing the map. - * However, the map preserves the key case (of the first insert or replace) and returns the keys in their - * original case in the appropriate methods (e.g. {@link FluentCaseInsensitiveStringsMap#keySet()}). - */ -public class FluentCaseInsensitiveStringsMap implements Map>, Iterable>> { - private final Map> values = new LinkedHashMap>(); - private final Map keyLookup = new LinkedHashMap(); - - public FluentCaseInsensitiveStringsMap() { - } - - public FluentCaseInsensitiveStringsMap(FluentCaseInsensitiveStringsMap src) { - if (src != null) { - for (Map.Entry> header : src) { - add(header.getKey(), header.getValue()); - } - } - } - - public FluentCaseInsensitiveStringsMap(Map> src) { - if (src != null) { - for (Map.Entry> header : src.entrySet()) { - add(header.getKey(), header.getValue()); - } - } - } - - public FluentCaseInsensitiveStringsMap add(String key, String value) { - if (key != null) { - String lcKey = key.toLowerCase(Locale.ENGLISH); - String realKey = keyLookup.get(lcKey); - - List curValues = null; - if (realKey == null) { - keyLookup.put(lcKey, key); - curValues = new ArrayList(); - values.put(key, curValues); - } else { - curValues = values.get(realKey); - } - - String nonNullValue = value != null? value : ""; - curValues.add(nonNullValue); - } - return this; - } - - /** - * Adds the specified values and returns this object. - * - * @param key The key - * @param values The value(s); if the array is null then this method has no effect. Individual null values are turned into empty strings - * @return This object - */ - public FluentCaseInsensitiveStringsMap add(String key, String... values) { - if (isNonEmpty(values)) { - add(key, Arrays.asList(values)); - } - return this; - } - - private List fetchValues(Collection values) { - List result = null; - - if (values != null) { - for (String value : values) { - if (value == null) { - value = ""; - } - if (result == null) { - // lazy initialization - result = new ArrayList(); - } - result.add(value); - } - } - return result; - } - - /** - * Adds the specified values and returns this object. - * - * @param key The key - * @param values The value(s); if null then this method has no effect. Use an empty collection - * to generate an empty value - * @return This object - */ - public FluentCaseInsensitiveStringsMap add(String key, Collection values) { - if (key != null) { - List nonNullValues = fetchValues(values); - - if (nonNullValues != null) { - String lcKey = key.toLowerCase(Locale.ENGLISH); - String realKey = keyLookup.get(lcKey); - List curValues = null; - - if (realKey == null) { - realKey = key; - keyLookup.put(lcKey, key); - } else { - curValues = this.values.get(realKey); - } - - if (curValues == null) { - curValues = new ArrayList(); - this.values.put(realKey, curValues); - } - curValues.addAll(nonNullValues); - } - } - return this; - } - - /** - * Adds all key-values pairs from the given object to this object and returns this object. - * - * @param src The source object - * @return This object - */ - public FluentCaseInsensitiveStringsMap addAll(FluentCaseInsensitiveStringsMap src) { - if (src != null) { - for (Map.Entry> header : src) { - add(header.getKey(), header.getValue()); - } - } - return this; - } - - /** - * Adds all key-values pairs from the given map to this object and returns this object. - * - * @param src The source map - * @return This object - */ - public FluentCaseInsensitiveStringsMap addAll(Map> src) { - if (src != null) { - for (Map.Entry> header : src.entrySet()) { - add(header.getKey(), header.getValue()); - } - } - return this; - } - - /** - * Replaces the values for the given key with the given values. - * - * @param key The key - * @param values The new values - * @return This object - */ - public FluentCaseInsensitiveStringsMap replace(final String key, final String... values) { - return replace(key, Arrays.asList(values)); - } - - /** - * Replaces the values for the given key with the given values. - * - * @param key The key - * @param values The new values - * @return This object - */ - public FluentCaseInsensitiveStringsMap replace(final String key, final Collection values) { - if (key != null) { - List nonNullValues = fetchValues(values); - String lcKkey = key.toLowerCase(Locale.ENGLISH); - String realKey = keyLookup.get(lcKkey); - - if (nonNullValues == null) { - keyLookup.remove(lcKkey); - if (realKey != null) { - this.values.remove(realKey); - } - } else { - if (!key.equals(realKey)) { - keyLookup.put(lcKkey, key); - this.values.remove(realKey); - } - this.values.put(key, nonNullValues); - } - } - return this; - } - - /** - * Replace the values for all keys from the given map that are also present in this object, with the values from the given map. - * All key-values from the given object that are not present in this object, will be added to it. - * - * @param src The source object - * @return This object - */ - public FluentCaseInsensitiveStringsMap replaceAll(FluentCaseInsensitiveStringsMap src) { - if (src != null) { - for (Map.Entry> header : src) { - replace(header.getKey(), header.getValue()); - } - } - return this; - } - - /** - * Replace the values for all keys from the given map that are also present in this object, with the values from the given map. - * All key-values from the given object that are not present in this object, will be added to it. - * - * @param src The source map - * @return This object - */ - public FluentCaseInsensitiveStringsMap replaceAll(Map> src) { - if (src != null) { - for (Map.Entry> header : src.entrySet()) { - replace(header.getKey(), header.getValue()); - } - } - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public List put(String key, List value) { - if (key == null) { - throw new NullPointerException("Null keys are not allowed"); - } - - List oldValue = get(key); - - replace(key, value); - return oldValue; - } - - /** - * {@inheritDoc} - */ - @Override - public void putAll(Map> values) { - replaceAll(values); - } - - /** - * Removes the values for the given key if present and returns this object. - * - * @param key The key - * @return This object - */ - public FluentCaseInsensitiveStringsMap delete(String key) { - if (key != null) { - String lcKey = key.toLowerCase(Locale.ENGLISH); - String realKey = keyLookup.remove(lcKey); - - if (realKey != null) { - values.remove(realKey); - } - } - return this; - } - - /** - * Removes the values for the given keys if present and returns this object. - * - * @param keys The keys - * @return This object - */ - public FluentCaseInsensitiveStringsMap deleteAll(String... keys) { - if (keys != null) { - for (String key : keys) { - remove(key); - } - } - return this; - } - - /** - * Removes the values for the given keys if present and returns this object. - * - * @param keys The keys - * @return This object - */ - public FluentCaseInsensitiveStringsMap deleteAll(Collection keys) { - if (keys != null) { - for (String key : keys) { - remove(key); - } - } - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public List remove(Object key) { - if (key == null) { - return null; - } else { - List oldValues = get(key.toString()); - - delete(key.toString()); - return oldValues; - } - } - - /** - * {@inheritDoc} - */ - @Override - public void clear() { - keyLookup.clear(); - values.clear(); - } - - /** - * {@inheritDoc} - */ - @Override - public Iterator>> iterator() { - return Collections.unmodifiableSet(values.entrySet()).iterator(); - } - - /** - * {@inheritDoc} - */ - @Override - public Set keySet() { - return new LinkedHashSet(keyLookup.values()); - } - - /** - * {@inheritDoc} - */ - @Override - public Set>> entrySet() { - return values.entrySet(); - } - - /** - * {@inheritDoc} - */ - @Override - public int size() { - return values.size(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isEmpty() { - return values.isEmpty(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean containsKey(Object key) { - return key == null ? false : keyLookup.containsKey(key.toString().toLowerCase(Locale.ENGLISH)); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean containsValue(Object value) { - return values.containsValue(value); - } - - /** - * Returns the value for the given key. If there are multiple values for this key, - * then only the first one will be returned. - * - * @param key The key - * @return The first value - */ - public String getFirstValue(String key) { - List values = get(key); - - if (values == null) { - return null; - } else if (values.isEmpty()) { - return ""; - } else { - return values.get(0); - } - } - - /** - * Returns the values for the given key joined into a single string using the given delimiter. - * - * @param key The key - * @return The value as a single string - */ - public String getJoinedValue(String key, String delimiter) { - List values = get(key); - - if (values == null) { - return null; - } else if (values.size() == 1) { - return values.get(0); - } else { - StringBuilder result = new StringBuilder(); - - for (String value : values) { - if (result.length() > 0) { - result.append(delimiter); - } - result.append(value); - } - return result.toString(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public List get(Object key) { - if (key == null) { - return null; - } - - String lcKey = key.toString().toLowerCase(Locale.ENGLISH); - String realKey = keyLookup.get(lcKey); - - if (realKey == null) { - return null; - } else { - return values.get(realKey); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Collection> values() { - return values.values(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - - final FluentCaseInsensitiveStringsMap other = (FluentCaseInsensitiveStringsMap) obj; - - if (values == null) { - if (other.values != null) { - return false; - } - } else if (!values.equals(other.values)) { - return false; - } - return true; - } - - @Override - public int hashCode() { - return values == null ? 0 : values.hashCode(); - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - - for (Map.Entry> entry : values.entrySet()) { - if (result.length() > 0) { - result.append("; "); - } - result.append("\""); - result.append(entry.getKey()); - result.append("="); - - boolean needsComma = false; - - for (String value : entry.getValue()) { - if (needsComma) { - result.append(", "); - } else { - needsComma = true; - } - result.append(value); - } - result.append("\""); - } - return result.toString(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/FluentStringsMap.java b/api/src/main/java/org/asynchttpclient/FluentStringsMap.java deleted file mode 100644 index 3e3c45f06e..0000000000 --- a/api/src/main/java/org/asynchttpclient/FluentStringsMap.java +++ /dev/null @@ -1,472 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * An implementation of a {@code String -> List} map that adds a fluent interface, i.e. methods that - * return this instance. - */ -public class FluentStringsMap implements Map>, Iterable>> { - private final Map> values = new LinkedHashMap>(); - - public FluentStringsMap() { - } - - public FluentStringsMap(FluentStringsMap src) { - if (src != null) { - for (Map.Entry> header : src) { - add(header.getKey(), header.getValue()); - } - } - } - - public FluentStringsMap(Map> src) { - if (src != null) { - for (Map.Entry> header : src.entrySet()) { - add(header.getKey(), header.getValue()); - } - } - } - - public FluentStringsMap add(String key, String value) { - if (key != null) { - List curValues = values.get(key); - - if (curValues == null) { - curValues = new ArrayList(1); - values.put(key, curValues); - } - curValues.add(value); - } - return this; - } - - /** - * Adds the specified values and returns this object. - * - * @param key The key - * @param values The value(s); if the array is null then this method has no effect - * @return This object - */ - public FluentStringsMap add(String key, String... values) { - if (isNonEmpty(values)) { - add(key, Arrays.asList(values)); - } - return this; - } - - /** - * Adds the specified values and returns this object. - * - * @param key The key - * @param values The value(s); if the array is null then this method has no effect - * @return This object - */ - public FluentStringsMap add(String key, Collection values) { - if (key != null && isNonEmpty(values)) { - List curValues = this.values.get(key); - - if (curValues == null) { - this.values.put(key, new ArrayList(values)); - } else { - curValues.addAll(values); - } - } - return this; - } - - /** - * Adds all key-values pairs from the given object to this object and returns this object. - * - * @param src The source object - * @return This object - */ - public FluentStringsMap addAll(FluentStringsMap src) { - if (src != null) { - for (Map.Entry> header : src) { - add(header.getKey(), header.getValue()); - } - } - return this; - } - - /** - * Adds all key-values pairs from the given map to this object and returns this object. - * - * @param src The source map - * @return This object - */ - public FluentStringsMap addAll(Map> src) { - if (src != null) { - for (Map.Entry> header : src.entrySet()) { - add(header.getKey(), header.getValue()); - } - } - return this; - } - - /** - * Replaces the values for the given key with the given values. - * - * @param key The key - * @param values The new values - * @return This object - */ - public FluentStringsMap replace(final String key, final String... values) { - return replace(key, Arrays.asList(values)); - } - - /** - * Replaces the values for the given key with the given values. - * - * @param key The key - * @param values The new values - * @return This object - */ - public FluentStringsMap replace(final String key, final Collection values) { - if (key != null) { - if (values == null) { - this.values.remove(key); - } else { - this.values.put(key, new ArrayList(values)); - } - } - return this; - } - - /** - * Replace the values for all keys from the given map that are also present in this object, with the values from the given map. - * All key-values from the given object that are not present in this object, will be added to it. - * - * @param src The source object - * @return This object - */ - public FluentStringsMap replaceAll(FluentStringsMap src) { - if (src != null) { - for (Map.Entry> header : src) { - replace(header.getKey(), header.getValue()); - } - } - return this; - } - - /** - * Replace the values for all keys from the given map that are also present in this object, with the values from the given map. - * All key-values from the given object that are not present in this object, will be added to it. - * - * @param src The source map - * @return This object - */ - public FluentStringsMap replaceAll(Map> src) { - if (src != null) { - for (Map.Entry> header : src.entrySet()) { - replace(header.getKey(), header.getValue()); - } - } - return this; - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public List put(String key, List value) { - if (key == null) { - throw new NullPointerException("Null keys are not allowed"); - } - - List oldValue = get(key); - - replace(key, value); - return oldValue; - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public void putAll(Map> values) { - replaceAll(values); - } - - /** - * Removes the values for the given key if present and returns this object. - * - * @param key The key - * @return This object - */ - public FluentStringsMap delete(String key) { - values.remove(key); - return this; - } - - /** - * Removes the values for the given keys if present and returns this object. - * - * @param keys The keys - * @return This object - */ - public FluentStringsMap deleteAll(String... keys) { - if (keys != null) { - for (String key : keys) { - remove(key); - } - } - return this; - } - - /** - * Removes the values for the given keys if present and returns this object. - * - * @param keys The keys - * @return This object - */ - public FluentStringsMap deleteAll(Collection keys) { - if (keys != null) { - for (String key : keys) { - remove(key); - } - } - return this; - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public List remove(Object key) { - if (key == null) { - return null; - } else { - List oldValues = get(key.toString()); - - delete(key.toString()); - return oldValues; - } - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public void clear() { - values.clear(); - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public Iterator>> iterator() { - return Collections.unmodifiableSet(values.entrySet()).iterator(); - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public Set keySet() { - return Collections.unmodifiableSet(values.keySet()); - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public Set>> entrySet() { - return values.entrySet(); - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public int size() { - return values.size(); - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public boolean isEmpty() { - return values.isEmpty(); - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public boolean containsKey(Object key) { - return key == null ? false : values.containsKey(key.toString()); - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public boolean containsValue(Object value) { - return values.containsValue(value); - } - - /** - * Returns the value for the given key. If there are multiple values for this key, - * then only the first one will be returned. - * - * @param key The key - * @return The first value - */ - public String getFirstValue(String key) { - List values = get(key); - - if (values == null) { - return null; - } else if (values.isEmpty()) { - return ""; - } else { - return values.get(0); - } - } - - /** - * Returns the values for the given key joined into a single string using the given delimiter. - * - * @param key The key - * @return The value as a single string - */ - public String getJoinedValue(String key, String delimiter) { - List values = get(key); - - if (values == null) { - return null; - } else if (values.size() == 1) { - return values.get(0); - } else { - StringBuilder result = new StringBuilder(); - - for (String value : values) { - if (result.length() > 0) { - result.append(delimiter); - } - result.append(value); - } - return result.toString(); - } - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public List get(Object key) { - if (key == null) { - return null; - } - - return values.get(key.toString()); - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public Collection> values() { - return values.values(); - } - - public List toParams() { - if (values.isEmpty()) - return Collections.emptyList(); - else { - List params = new ArrayList(values.size()); - for (Map.Entry> entry : values.entrySet()) { - String name = entry.getKey(); - for (String value: entry.getValue()) - params.add(new Param(name, value)); - } - return params; - } - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - - final FluentStringsMap other = (FluentStringsMap) obj; - - if (values == null) { - if (other.values != null) { - return false; - } - } else if (!values.equals(other.values)) { - return false; - } - return true; - } - - @Override - public int hashCode() { - return values == null ? 0 : values.hashCode(); - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - - for (Map.Entry> entry : values.entrySet()) { - if (result.length() > 0) { - result.append("; "); - } - result.append("\""); - result.append(entry.getKey()); - result.append("="); - - boolean needsComma = false; - - for (String value : entry.getValue()) { - if (needsComma) { - result.append(", "); - } else { - needsComma = true; - } - result.append(value); - } - result.append("\""); - } - return result.toString(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java b/api/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java deleted file mode 100644 index b7ab7c7e9f..0000000000 --- a/api/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * A callback class used when an HTTP response body is received. - */ -public abstract class HttpResponseBodyPart { - - /** - * Return length of this part in bytes. - * - * @since 2.0.0 - */ - public abstract int length(); - - /** - * Return the response body's part bytes received. - * - * @return the response body's part bytes received. - */ - public abstract byte[] getBodyPartBytes(); - - /** - * Method for accessing contents of this part via stream. - * - * @since 2.0.0 - */ - public abstract InputStream readBodyPartBytes(); - - /** - * Write the available bytes to the {@link java.io.OutputStream} - * - * @param outputStream - * @return The number of bytes written - * @throws IOException - */ - public abstract int writeTo(OutputStream outputStream) throws IOException; - - /** - * Return a {@link ByteBuffer} that wraps the actual bytes read from the response's chunk. The {@link ByteBuffer} - * capacity is equal to the number of bytes available. - * - * @return {@link ByteBuffer} - */ - public abstract ByteBuffer getBodyByteBuffer(); - - /** - * Return true if this is the last part. - * - * @return true if this is the last part. - */ - public abstract boolean isLast(); - - /** - * Close the underlying connection once the processing has completed. Invoking that method means the - * underlying TCP connection will be closed as soon as the processing of the response is completed. That - * means the underlying connection will never get pooled. - */ - public abstract void markUnderlyingConnectionAsToBeClosed(); - - /** - * Return true of the underlying connection will be closed once the response has been fully processed. - * - * @return true of the underlying connection will be closed once the response has been fully processed. - */ - public abstract boolean isUnderlyingConnectionToBeClosed(); - -} diff --git a/api/src/main/java/org/asynchttpclient/HttpResponseHeaders.java b/api/src/main/java/org/asynchttpclient/HttpResponseHeaders.java deleted file mode 100644 index b573fd8766..0000000000 --- a/api/src/main/java/org/asynchttpclient/HttpResponseHeaders.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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; - -/** - * A class that represent the HTTP headers. - */ -public abstract class HttpResponseHeaders { - - private final boolean traillingHeaders; - - public HttpResponseHeaders() { - this.traillingHeaders = false; - } - - public HttpResponseHeaders(boolean traillingHeaders) { - this.traillingHeaders = traillingHeaders; - } - - /** - * Return the HTTP header - * - * @return an {@link FluentCaseInsensitiveStringsMap} - */ - abstract public FluentCaseInsensitiveStringsMap getHeaders(); - - /** - * Return true is headers has been received after the response body. - * - * @return true is headers has been received after the response body. - */ - public boolean isTraillingHeadersReceived() { - return traillingHeaders; - } -} diff --git a/api/src/main/java/org/asynchttpclient/HttpResponseStatus.java b/api/src/main/java/org/asynchttpclient/HttpResponseStatus.java deleted file mode 100644 index ecf18cd980..0000000000 --- a/api/src/main/java/org/asynchttpclient/HttpResponseStatus.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 java.util.List; - -import org.asynchttpclient.uri.UriComponents; - -/** - * A class that represent the HTTP response' status line (code + text) - */ -public abstract class HttpResponseStatus { - - private final UriComponents uri; - protected final AsyncHttpClientConfig config; - - public HttpResponseStatus(UriComponents uri, AsyncHttpClientConfig config) { - this.uri = uri; - this.config = config; - } - - /** - * Return the request {@link UriComponents} - * - * @return the request {@link UriComponents} - */ - public final UriComponents getUri() { - return uri; - } - - /** - * Prepare a {@link Response} - * - * @param headers {@link HttpResponseHeaders} - * @param bodyParts list of {@link HttpResponseBodyPart} - * @param config the client config - * @return a {@link Response} - */ - public abstract Response prepareResponse(HttpResponseHeaders headers, List bodyParts); - - /** - * Return the response status code - * - * @return the response status code - */ - public abstract int getStatusCode(); - - /** - * Return the response status text - * - * @return the response status text - */ - public abstract String getStatusText(); - - /** - * Protocol name from status line. - * - * @return Protocol name. - */ - public abstract String getProtocolName(); - - /** - * Protocol major version. - * - * @return Major version. - */ - public abstract int getProtocolMajorVersion(); - - /** - * Protocol minor version. - * - * @return Minor version. - */ - public abstract int getProtocolMinorVersion(); - - /** - * Full protocol name + version - * - * @return protocol name + version - */ - public abstract String getProtocolText(); -} diff --git a/api/src/main/java/org/asynchttpclient/ListenableFuture.java b/api/src/main/java/org/asynchttpclient/ListenableFuture.java deleted file mode 100755 index 8d2ca73dc3..0000000000 --- a/api/src/main/java/org/asynchttpclient/ListenableFuture.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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. - */ -/* - * Copyright (C) 2007 Google Inc. - * - * 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 java.util.concurrent.Executor; -import java.util.concurrent.Future; - -/** - * Extended {@link Future} - * - * @param Type of the value that will be returned. - */ -public interface ListenableFuture extends Future { - - /** - * Terminate and if there is no exception, mark this Future as done and release the internal lock. - * - * @param callable - */ - void done(); - - /** - * Abort the current processing, and propagate the {@link Throwable} to the {@link AsyncHandler} or {@link Future} - * - * @param t - */ - void abort(Throwable t); - - /** - * Touch the current instance to prevent external service to times out. - */ - void touch(); - - /** - *

Adds a listener and executor to the ListenableFuture. - * The listener will be {@linkplain java.util.concurrent.Executor#execute(Runnable) passed - * to the executor} for execution when the {@code Future}'s computation is - * {@linkplain Future#isDone() complete}. - *

- *

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, - * but any listener added through this method is guaranteed to be called once - * the computation is complete. - * - * @param listener the listener to run when the computation is complete. - * @param exec the executor to run the listener in. - * @return this Future - * @throws NullPointerException if the executor or listener was null. - * @throws java.util.concurrent.RejectedExecutionException - * if we tried to execute the listener - * immediately but the executor rejected it. - */ - ListenableFuture addListener(Runnable listener, Executor exec); -} diff --git a/api/src/main/java/org/asynchttpclient/MaxRedirectException.java b/api/src/main/java/org/asynchttpclient/MaxRedirectException.java deleted file mode 100644 index f39f70d900..0000000000 --- a/api/src/main/java/org/asynchttpclient/MaxRedirectException.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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; - -/** - * Thrown when the {@link AsyncHttpClientConfig#getMaxRedirects()} has been reached. - */ -public class MaxRedirectException extends Exception { - private static final long serialVersionUID = 1L; - - public MaxRedirectException() { - super(); - } - - public MaxRedirectException(String msg) { - super(msg); - } - - public MaxRedirectException(Throwable cause) { - super(cause); - } - - public MaxRedirectException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/api/src/main/java/org/asynchttpclient/Param.java b/api/src/main/java/org/asynchttpclient/Param.java deleted file mode 100644 index 788f66c425..0000000000 --- a/api/src/main/java/org/asynchttpclient/Param.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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; - -/** - * A pair of (name, value) String - * @author slandelle - */ -public class Param { - - private final String name; - private final String value; - public Param(String name, String value) { - this.name = name; - this.value = value; - } - public String getName() { - return name; - } - public String getValue() { - return value; - } - - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((value == null) ? 0 : value.hashCode()); - return result; - } - - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof Param)) - return false; - Param other = (Param) obj; - if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - if (value == null) { - if (other.value != null) - return false; - } else if (!value.equals(other.value)) - return false; - return true; - } -} diff --git a/api/src/main/java/org/asynchttpclient/ProgressAsyncHandler.java b/api/src/main/java/org/asynchttpclient/ProgressAsyncHandler.java deleted file mode 100644 index bde0d5dd52..0000000000 --- a/api/src/main/java/org/asynchttpclient/ProgressAsyncHandler.java +++ /dev/null @@ -1,47 +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; - -/** - * An extended {@link AsyncHandler} with two extra callback who get invoked during the content upload to a remote server. - * This {@link AsyncHandler} must be used only with PUT and POST request. - */ -public interface ProgressAsyncHandler extends AsyncHandler { - - /** - * Invoked when the content (a {@link java.io.File}, {@link String} or {@link java.io.FileInputStream} has been fully - * written on the I/O socket. - * - * @return a {@link AsyncHandler.STATE} telling to CONTINUE or ABORT the current processing. - */ - STATE onHeaderWriteCompleted(); - - /** - * Invoked when the content (a {@link java.io.File}, {@link String} or {@link java.io.FileInputStream} has been fully - * written on the I/O socket. - * - * @return a {@link AsyncHandler.STATE} telling to CONTINUE or ABORT the current processing. - */ - STATE onContentWriteCompleted(); - - /** - * Invoked when the I/O operation associated with the {@link Request} body wasn't fully written in a single I/O write - * operation. This method is never invoked if the write operation complete in a sinfle I/O write. - * - * @param amount The amount of bytes to transfer. - * @param current The amount of bytes transferred - * @param total The total number of bytes transferred - * @return a {@link AsyncHandler.STATE} telling to CONTINUE or ABORT the current processing. - */ - STATE onContentWriteProgress(long amount, long current, long total); -} diff --git a/api/src/main/java/org/asynchttpclient/ProxyServer.java b/api/src/main/java/org/asynchttpclient/ProxyServer.java deleted file mode 100644 index 3c6a5ff429..0000000000 --- a/api/src/main/java/org/asynchttpclient/ProxyServer.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 org.asynchttpclient.util.StandardCharsets; - -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Represents a proxy server. - */ -public class ProxyServer { - - public enum Protocol { - HTTP("http"), HTTPS("https"), NTLM("NTLM"), KERBEROS("KERBEROS"), SPNEGO("SPNEGO"); - - private final String protocol; - - private Protocol(final String protocol) { - this.protocol = protocol; - } - - public String getProtocol() { - return protocol; - } - - @Override - public String toString() { - return getProtocol(); - } - } - - private final List nonProxyHosts = new ArrayList(); - private final Protocol protocol; - private final String host; - private final String principal; - private final String password; - private final int port; - private final String url; - private String encoding = StandardCharsets.UTF_8.name(); - private Charset charset = StandardCharsets.UTF_8; - private String ntlmDomain = System.getProperty("http.auth.ntlm.domain", ""); - - public ProxyServer(final Protocol protocol, final String host, final int port, String principal, String password) { - this.protocol = protocol; - this.host = host; - this.port = port; - this.principal = principal; - this.password = password; - url = protocol + "://" + host + ":" + port; - } - - public ProxyServer(final String host, final int port, String principal, String password) { - this(Protocol.HTTP, host, port, principal, password); - } - - public ProxyServer(final Protocol protocol, final String host, final int port) { - this(protocol, host, port, null, null); - } - - public ProxyServer(final String host, final int port) { - this(Protocol.HTTP, host, port, null, null); - } - - public Protocol getProtocol() { - return protocol; - } - - public String getProtocolAsString() { - return protocol.toString(); - } - - public String getHost() { - return host; - } - - public int getPort() { - return port; - } - - public String getPrincipal() { - return principal; - } - - public String getPassword() { - return password; - } - - public ProxyServer setEncoding(String encoding) { - this.encoding = encoding; - this.charset = Charset.forName(encoding); - return this; - } - - public String getEncoding() { - return encoding; - } - - public Charset getCharset() { - return charset; - } - - public String getUrl() { - return url; - } - - public ProxyServer addNonProxyHost(String uri) { - nonProxyHosts.add(uri); - return this; - } - - public ProxyServer removeNonProxyHost(String uri) { - nonProxyHosts.remove(uri); - return this; - } - - public List getNonProxyHosts() { - return Collections.unmodifiableList(nonProxyHosts); - } - - public ProxyServer setNtlmDomain(String ntlmDomain) { - this.ntlmDomain = ntlmDomain; - return this; - } - - public String getNtlmDomain() { - return ntlmDomain; - } - - @Override - public String toString() { - return url; - } -} diff --git a/api/src/main/java/org/asynchttpclient/ProxyServerSelector.java b/api/src/main/java/org/asynchttpclient/ProxyServerSelector.java deleted file mode 100644 index 84862331f1..0000000000 --- a/api/src/main/java/org/asynchttpclient/ProxyServerSelector.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.asynchttpclient; - -import org.asynchttpclient.uri.UriComponents; - -/** - * Selector for a proxy server - */ -public interface ProxyServerSelector { - - /** - * Select a proxy server to use for the given URI. - * - * @param uri The URI to select a proxy server for. - * @return The proxy server to use, if any. May return null. - */ - ProxyServer select(UriComponents uri); - - /** - * A selector that always selects no proxy. - */ - static final ProxyServerSelector NO_PROXY_SELECTOR = new ProxyServerSelector() { - @Override - public ProxyServer select(UriComponents uri) { - return null; - } - }; -} diff --git a/api/src/main/java/org/asynchttpclient/RandomAccessBody.java b/api/src/main/java/org/asynchttpclient/RandomAccessBody.java deleted file mode 100644 index fa74e9e684..0000000000 --- a/api/src/main/java/org/asynchttpclient/RandomAccessBody.java +++ /dev/null @@ -1,36 +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; - -import java.io.IOException; -import java.nio.channels.WritableByteChannel; - -/** - * A request body which supports random access to its contents. - */ -public interface RandomAccessBody extends Body { - - /** - * Transfers the specified chunk of bytes from this body to the specified channel. - * - * @param position - * The zero-based byte index from which to start the transfer, must not be negative. - * @param target - * The destination channel to transfer the body chunk to, must not be {@code null}. - * @return The non-negative number of bytes actually transferred. - * @throws IOException - * If the body chunk could not be transferred. - */ - long transferTo(long position, WritableByteChannel target) throws IOException; -} diff --git a/api/src/main/java/org/asynchttpclient/Realm.java b/api/src/main/java/org/asynchttpclient/Realm.java deleted file mode 100644 index 1f5434f62a..0000000000 --- a/api/src/main/java/org/asynchttpclient/Realm.java +++ /dev/null @@ -1,635 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.StandardCharsets; - -import java.nio.charset.Charset; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * This class is required when authentication is needed. The class support DIGEST and BASIC. - */ -public class Realm { - - private static final String NC = "00000001"; - - private final String principal; - private final String password; - private final AuthScheme scheme; - private final String realmName; - private final String nonce; - private final String algorithm; - private final String response; - private final String opaque; - private final String qop; - private final String nc; - private final String cnonce; - private final UriComponents uri; - private final String methodName; - private final boolean usePreemptiveAuth; - private final String enc; - private final String host; - private final boolean messageType2Received; - private final String ntlmDomain; - private final Charset charset; - private final boolean useAbsoluteURI; - private final boolean omitQuery; - private final boolean targetProxy; - - public enum AuthScheme { - DIGEST, BASIC, NTLM, SPNEGO, KERBEROS, NONE - } - - private Realm(AuthScheme scheme, String principal, String password, String realmName, String nonce, String algorithm, String response, - String qop, String nc, String cnonce, UriComponents uri, String method, boolean usePreemptiveAuth, String ntlmDomain, String enc, - String host, boolean messageType2Received, String opaque, boolean useAbsoluteURI, boolean omitQuery, boolean targetProxy) { - - this.principal = principal; - this.password = password; - this.scheme = scheme; - this.realmName = realmName; - this.nonce = nonce; - this.algorithm = algorithm; - this.response = response; - this.opaque = opaque; - this.qop = qop; - this.nc = nc; - this.cnonce = cnonce; - this.uri = uri; - this.methodName = method; - this.usePreemptiveAuth = usePreemptiveAuth; - this.ntlmDomain = ntlmDomain; - this.enc = enc; - this.host = host; - this.messageType2Received = messageType2Received; - this.charset = enc != null ? Charset.forName(enc) : null; - this.useAbsoluteURI = useAbsoluteURI; - this.omitQuery = omitQuery; - this.targetProxy = targetProxy; - } - - public String getPrincipal() { - return principal; - } - - public String getPassword() { - return password; - } - - public AuthScheme getAuthScheme() { - return scheme; - } - - public AuthScheme getScheme() { - - return scheme; - } - - public String getRealmName() { - return realmName; - } - - public String getNonce() { - return nonce; - } - - public String getAlgorithm() { - return algorithm; - } - - public String getResponse() { - return response; - } - - public String getOpaque() { - return opaque; - } - - public String getQop() { - return qop; - } - - public String getNc() { - return nc; - } - - public String getCnonce() { - return cnonce; - } - - public UriComponents getUri() { - return uri; - } - - public String getEncoding() { - return enc; - } - - public Charset getCharset() { - return charset; - } - - public String getMethodName() { - return methodName; - } - - /** - * Return true is preemptive authentication is enabled - * - * @return true is preemptive authentication is enabled - */ - public boolean getUsePreemptiveAuth() { - return usePreemptiveAuth; - } - - /** - * Return the NTLM domain to use. This value should map the JDK - * - * @return the NTLM domain - */ - public String getNtlmDomain() { - return ntlmDomain; - } - - /** - * Return the NTLM host. - * - * @return the NTLM host - */ - public String getNtlmHost() { - return host; - } - - public boolean isNtlmMessageType2Received() { - return messageType2Received; - } - - public boolean isUseAbsoluteURI() { - return useAbsoluteURI; - } - - public boolean isOmitQuery() { - return omitQuery; - } - - public boolean isTargetProxy() { - return targetProxy; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - - Realm realm = (Realm) o; - - if (algorithm != null ? !algorithm.equals(realm.algorithm) : realm.algorithm != null) - return false; - if (cnonce != null ? !cnonce.equals(realm.cnonce) : realm.cnonce != null) - return false; - if (nc != null ? !nc.equals(realm.nc) : realm.nc != null) - return false; - if (nonce != null ? !nonce.equals(realm.nonce) : realm.nonce != null) - return false; - if (password != null ? !password.equals(realm.password) : realm.password != null) - return false; - if (principal != null ? !principal.equals(realm.principal) : realm.principal != null) - return false; - if (qop != null ? !qop.equals(realm.qop) : realm.qop != null) - return false; - if (realmName != null ? !realmName.equals(realm.realmName) : realm.realmName != null) - return false; - if (response != null ? !response.equals(realm.response) : realm.response != null) - return false; - if (scheme != realm.scheme) - return false; - if (uri != null ? !uri.equals(realm.uri) : realm.uri != null) - return false; - if (useAbsoluteURI != !realm.useAbsoluteURI) return false; - if (omitQuery != !realm.omitQuery) return false; - return true; - } - - @Override - public String toString() { - return "Realm{" + "principal='" + principal + '\'' + ", scheme=" + scheme + ", realmName='" + realmName + '\'' + ", nonce='" - + nonce + '\'' + ", algorithm='" + algorithm + '\'' + ", response='" + response + '\'' + ", qop='" + qop + '\'' + ", nc='" - + nc + '\'' + ", cnonce='" + cnonce + '\'' + ", uri='" + uri + '\'' + ", methodName='" + methodName + '\'' + ", useAbsoluteURI='" + useAbsoluteURI + '\'' - + ", omitQuery='" + omitQuery + '\'' +'}'; - } - - @Override - public int hashCode() { - int result = principal != null ? principal.hashCode() : 0; - result = 31 * result + (password != null ? password.hashCode() : 0); - result = 31 * result + (scheme != null ? scheme.hashCode() : 0); - result = 31 * result + (realmName != null ? realmName.hashCode() : 0); - result = 31 * result + (nonce != null ? nonce.hashCode() : 0); - result = 31 * result + (algorithm != null ? algorithm.hashCode() : 0); - result = 31 * result + (response != null ? response.hashCode() : 0); - result = 31 * result + (qop != null ? qop.hashCode() : 0); - result = 31 * result + (nc != null ? nc.hashCode() : 0); - result = 31 * result + (cnonce != null ? cnonce.hashCode() : 0); - result = 31 * result + (uri != null ? uri.hashCode() : 0); - return result; - } - - /** - * A builder for {@link Realm} - */ - public static class RealmBuilder { - // - // Portions of code (newCnonce, newResponse) are highly inspired be Jetty 6 BasicAuthentication.java class. - // This code is already Apache licenced. - // - - private String principal = ""; - private String password = ""; - private AuthScheme scheme = AuthScheme.NONE; - private String realmName = ""; - private String nonce = ""; - private String algorithm = "MD5"; - private String response = ""; - private String opaque = ""; - private String qop = "auth"; - private String nc = "00000001"; - private String cnonce = ""; - private UriComponents uri; - private String methodName = "GET"; - private boolean usePreemptive; - private String ntlmDomain = System.getProperty("http.auth.ntlm.domain", ""); - private String enc = StandardCharsets.UTF_8.name(); - private String host = "localhost"; - private boolean messageType2Received; - private boolean useAbsoluteURI = true; - private boolean omitQuery; - private boolean targetProxy; - - public String getNtlmDomain() { - return ntlmDomain; - } - - public RealmBuilder setNtlmDomain(String ntlmDomain) { - this.ntlmDomain = ntlmDomain; - return this; - } - - public String getNtlmHost() { - return host; - } - - public RealmBuilder setNtlmHost(String host) { - this.host = host; - return this; - } - - public String getPrincipal() { - return principal; - } - - public RealmBuilder setPrincipal(String principal) { - this.principal = principal; - return this; - } - - public String getPassword() { - return password; - } - - public RealmBuilder setPassword(String password) { - this.password = password; - return this; - } - - public AuthScheme getScheme() { - return scheme; - } - - public RealmBuilder setScheme(AuthScheme scheme) { - this.scheme = scheme; - return this; - } - - public String getRealmName() { - return realmName; - } - - public RealmBuilder setRealmName(String realmName) { - this.realmName = realmName; - return this; - } - - public String getNonce() { - return nonce; - } - - public RealmBuilder setNonce(String nonce) { - this.nonce = nonce; - return this; - } - - public String getAlgorithm() { - return algorithm; - } - - public RealmBuilder setAlgorithm(String algorithm) { - this.algorithm = algorithm; - return this; - } - - public String getResponse() { - return response; - } - - public RealmBuilder setResponse(String response) { - this.response = response; - return this; - } - - public String getOpaque() { - return this.opaque; - } - - public RealmBuilder setOpaque(String opaque) { - this.opaque = opaque; - return this; - } - - public String getQop() { - return qop; - } - - public RealmBuilder setQop(String qop) { - this.qop = qop; - return this; - } - - public String getNc() { - return nc; - } - - public RealmBuilder setNc(String nc) { - this.nc = nc; - return this; - } - - public UriComponents getUri() { - return uri; - } - - public RealmBuilder setUri(UriComponents uri) { - this.uri = uri; - return this; - } - - public String getMethodName() { - return methodName; - } - - public RealmBuilder setMethodName(String methodName) { - this.methodName = methodName; - return this; - } - - public boolean getUsePreemptiveAuth() { - return usePreemptive; - } - - public RealmBuilder setUsePreemptiveAuth(boolean usePreemptiveAuth) { - this.usePreemptive = usePreemptiveAuth; - return this; - } - - public RealmBuilder setNtlmMessageType2Received(boolean messageType2Received) { - this.messageType2Received = messageType2Received; - return this; - } - - public boolean isUseAbsoluteURI() { - return useAbsoluteURI; - } - - public RealmBuilder setUseAbsoluteURI(boolean useAbsoluteURI) { - this.useAbsoluteURI = useAbsoluteURI; - return this; - } - - public boolean isOmitQuery() { - return omitQuery; - } - - public RealmBuilder setOmitQuery(boolean omitQuery) { - this.omitQuery = omitQuery; - return this; - } - - public boolean isTargetProxy() { - return targetProxy; - } - - public RealmBuilder setTargetProxy(boolean targetProxy) { - this.targetProxy = targetProxy; - return this; - } - - public RealmBuilder parseWWWAuthenticateHeader(String headerLine) { - setRealmName(match(headerLine, "realm")); - setNonce(match(headerLine, "nonce")); - String algorithm = match(headerLine, "algorithm"); - if (isNonEmpty(algorithm)) { - setAlgorithm(algorithm); - } - setOpaque(match(headerLine, "opaque")); - setQop(match(headerLine, "qop")); - if (isNonEmpty(getNonce())) { - setScheme(AuthScheme.DIGEST); - } else { - setScheme(AuthScheme.BASIC); - } - return this; - } - - public RealmBuilder parseProxyAuthenticateHeader(String headerLine) { - setRealmName(match(headerLine, "realm")); - setNonce(match(headerLine, "nonce")); - setOpaque(match(headerLine, "opaque")); - setQop(match(headerLine, "qop")); - if (isNonEmpty(getNonce())) { - setScheme(AuthScheme.DIGEST); - } else { - setScheme(AuthScheme.BASIC); - } - setTargetProxy(true); - return this; - } - - public RealmBuilder clone(Realm clone) { - setRealmName(clone.getRealmName()); - setAlgorithm(clone.getAlgorithm()); - setMethodName(clone.getMethodName()); - setNc(clone.getNc()); - setNonce(clone.getNonce()); - setPassword(clone.getPassword()); - setPrincipal(clone.getPrincipal()); - setEncoding(clone.getEncoding()); - setOpaque(clone.getOpaque()); - setQop(clone.getQop()); - setScheme(clone.getScheme()); - setUri(clone.getUri()); - setUsePreemptiveAuth(clone.getUsePreemptiveAuth()); - setNtlmDomain(clone.getNtlmDomain()); - setNtlmHost(clone.getNtlmHost()); - setNtlmMessageType2Received(clone.isNtlmMessageType2Received()); - setUseAbsoluteURI(clone.isUseAbsoluteURI()); - setOmitQuery(clone.isOmitQuery()); - setTargetProxy(clone.isTargetProxy()); - return this; - } - - private void newCnonce() { - try { - MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] b = md.digest(String.valueOf(System.currentTimeMillis()).getBytes(StandardCharsets.ISO_8859_1)); - cnonce = toHexString(b); - } catch (Exception e) { - throw new SecurityException(e); - } - } - - /** - * TODO: A Pattern/Matcher may be better. - */ - private String match(String headerLine, String token) { - if (headerLine == null) { - return ""; - } - - int match = headerLine.indexOf(token); - if (match <= 0) - return ""; - - // = to skip - match += token.length() + 1; - int trailingComa = headerLine.indexOf(",", match); - String value = headerLine.substring(match, trailingComa > 0 ? trailingComa : headerLine.length()); - value = value.length() > 0 && value.charAt(value.length() - 1) == '"' ? value.substring(0, value.length() - 1) : value; - return value.charAt(0) == '"' ? value.substring(1) : value; - } - - public String getEncoding() { - return enc; - } - - public RealmBuilder setEncoding(String enc) { - this.enc = enc; - return this; - } - - private void newResponse() { - MessageDigest md = null; - try { - md = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - throw new SecurityException(e); - } - md.update(new StringBuilder(principal).append(":")// - .append(realmName).append(":")// - .append(password).toString()// - .getBytes(StandardCharsets.ISO_8859_1)); - byte[] ha1 = md.digest(); - - md.reset(); - - // HA2 if qop is auth-int is methodName:url:md5(entityBody) - md.update(new StringBuilder(methodName).append(':')// - .append(uri).toString()// - .getBytes(StandardCharsets.ISO_8859_1)); - byte[] ha2 = md.digest(); - - if (qop == null || qop.length() == 0) { - md.update(new StringBuilder(toBase16(ha1)).append(':')// - .append(nonce).append(':')// - .append(toBase16(ha2)).toString()// - .getBytes(StandardCharsets.ISO_8859_1)); - - } else { - // qop ="auth" or "auth-int" - md.update(new StringBuilder(toBase16(ha1)).append(':')// - .append(nonce).append(':')// - .append(NC).append(':')// - .append(cnonce).append(':')// - .append(qop).append(':')// - .append(toBase16(ha2)).toString()// - .getBytes(StandardCharsets.ISO_8859_1)); - } - - byte[] digest = md.digest(); - - response = toHexString(digest); - } - - private static String toHexString(byte[] data) { - StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < data.length; i++) { - buffer.append(Integer.toHexString((data[i] & 0xf0) >>> 4)); - buffer.append(Integer.toHexString(data[i] & 0x0f)); - } - return buffer.toString(); - } - - private static String toBase16(byte[] bytes) { - int base = 16; - StringBuilder buf = new StringBuilder(); - for (byte b : bytes) { - int bi = 0xff & b; - int c = '0' + (bi / base) % base; - if (c > '9') - c = 'a' + (c - '0' - 10); - buf.append((char) c); - c = '0' + bi % base; - if (c > '9') - c = 'a' + (c - '0' - 10); - buf.append((char) c); - } - return buf.toString(); - } - - /** - * Build a {@link Realm} - * - * @return a {@link Realm} - */ - public Realm build() { - - // Avoid generating - if (isNonEmpty(nonce)) { - newCnonce(); - newResponse(); - } - - return new Realm(scheme, principal, password, realmName, nonce, algorithm, response, qop, nc, cnonce, uri, methodName, - usePreemptive, ntlmDomain, enc, host, messageType2Received, opaque, useAbsoluteURI, omitQuery, targetProxy); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/Request.java b/api/src/main/java/org/asynchttpclient/Request.java deleted file mode 100644 index e292e8b5e8..0000000000 --- a/api/src/main/java/org/asynchttpclient/Request.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.multipart.Part; -import org.asynchttpclient.uri.UriComponents; - -import java.io.File; -import java.io.InputStream; -import java.net.InetAddress; -import java.util.Collection; -import java.util.List; - -/** - * The Request class can be used to construct HTTP request: - *

- *   Request r = new RequestBuilder().setUrl("url")
- *                      .setRealm((new Realm.RealmBuilder()).setPrincipal(user)
- *                      .setPassword(admin)
- *                      .setRealmName("MyRealm")
- *                      .setScheme(Realm.AuthScheme.DIGEST).build());
- * 
- */ -public interface Request { - - /** - * Return the request's method name (GET, POST, etc.) - * - * @return the request's method name (GET, POST, etc.) - */ - String getMethod(); - - UriComponents getURI(); - - /** - * Return the InetAddress to override - * - * @return the InetAddress - */ - InetAddress getInetAddress(); - - InetAddress getLocalAddress(); - - /** - * Return the current set of Headers. - * - * @return a {@link FluentCaseInsensitiveStringsMap} contains headers. - */ - FluentCaseInsensitiveStringsMap getHeaders(); - - /** - * Return Coookie. - * - * @return an unmodifiable Collection of Cookies - */ - Collection getCookies(); - - /** - * Return the current request's body as a byte array - * - * @return a byte array of the current request's body. - */ - byte[] getByteData(); - - /** - * Return the current request's body as a string - * - * @return an String representation of the current request's body. - */ - String getStringData(); - - /** - * Return the current request's body as an InputStream - * - * @return an InputStream representation of the current request's body. - */ - InputStream getStreamData(); - - /** - * Return the current request's body generator. - * - * @return A generator for the request body. - */ - BodyGenerator getBodyGenerator(); - - /** - * Return the current size of the content-lenght header based on the body's size. - * - * @return the current size of the content-lenght header based on the body's size. - */ - long getContentLength(); - - /** - * Return the current form parameters. - * - * @return a {@link FluentStringsMap} of parameters. - */ - List getFormParams(); - - /** - * Return the current {@link Part} - * - * @return the current {@link Part} - */ - List getParts(); - - /** - * Return the virtual host value. - * - * @return the virtual host value. - */ - String getVirtualHost(); - - /** - * Return the query params. - * - * @return {@link FluentStringsMap} of query string - */ - List getQueryParams(); - - /** - * Return the {@link ProxyServer} - * - * @return the {@link ProxyServer} - */ - ProxyServer getProxyServer(); - - /** - * Return the {@link Realm} - * - * @return the {@link Realm} - */ - Realm getRealm(); - - /** - * Return the {@link File} to upload. - * - * @return the {@link File} to upload. - */ - File getFile(); - - /** - * Return follow redirect - * - * @return the TRUE> to follow redirect, FALSE, if NOT to follow, whatever the client config. - * Return null if not set. - */ - Boolean getFollowRedirect(); - - /** - * Overrides the config default value - * @return the request timeout - */ - int getRequestTimeoutInMs(); - - /** - * Return the HTTP Range header value, or - * - * @return the range header value, or 0 is not set. - */ - long getRangeOffset(); - - /** - * Return the encoding value used when encoding the request's body. - * - * @return the encoding value used when encoding the request's body. - */ - String getBodyEncoding(); - - ConnectionPoolKeyStrategy getConnectionPoolKeyStrategy(); -} diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilder.java b/api/src/main/java/org/asynchttpclient/RequestBuilder.java deleted file mode 100644 index b5d2e91444..0000000000 --- a/api/src/main/java/org/asynchttpclient/RequestBuilder.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 java.io.InputStream; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.multipart.Part; -import org.asynchttpclient.util.QueryComputer; - -/** - * Builder for a {@link Request}. - * Warning: mutable and not thread-safe! Beware that it holds a reference on the Request instance it builds, - * so modifying the builder will modify the request even after it has been built. - */ -public class RequestBuilder extends RequestBuilderBase { - - public RequestBuilder() { - super(RequestBuilder.class, "GET", false); - } - - public RequestBuilder(String method) { - super(RequestBuilder.class, method, false); - } - - public RequestBuilder(String method, boolean useRawUrl) { - super(RequestBuilder.class, method, useRawUrl); - } - - public RequestBuilder(String method, QueryComputer queryComputer) { - super(RequestBuilder.class, method, queryComputer); - } - - public RequestBuilder(Request prototype) { - super(RequestBuilder.class, prototype); - } - - public RequestBuilder(Request prototype, QueryComputer queryComputer) { - super(RequestBuilder.class, prototype, queryComputer); - } - - // Note: For now we keep the delegates in place even though they are not needed - // since otherwise Clojure (and maybe other languages) won't be able to - // access these methods - see Clojure tickets 126 and 259 - - @Override - public RequestBuilder addBodyPart(Part part) { - return super.addBodyPart(part); - } - - @Override - public RequestBuilder addCookie(Cookie cookie) { - return super.addCookie(cookie); - } - - @Override - public RequestBuilder addHeader(String name, String value) { - return super.addHeader(name, value); - } - - @Override - public RequestBuilder addFormParam(String key, String value) { - return super.addFormParam(key, value); - } - - @Override - public RequestBuilder addQueryParam(String name, String value) { - return super.addQueryParam(name, value); - } - - @Override - public RequestBuilder addQueryParams(List queryParams) { - return super.addQueryParams(queryParams); - } - - @Override - public RequestBuilder setQueryParams(List params) { - return super.setQueryParams(params); - } - - @Override - public RequestBuilder setQueryParams(Map> params) { - return super.setQueryParams(params); - } - - @Override - public Request build() { - return super.build(); - } - - @Override - public RequestBuilder setBody(byte[] data) { - return super.setBody(data); - } - - @Override - public RequestBuilder setBody(InputStream stream) { - return super.setBody(stream); - } - - @Override - public RequestBuilder setBody(String data) { - return super.setBody(data); - } - - @Override - public RequestBuilder setHeader(String name, String value) { - return super.setHeader(name, value); - } - - @Override - public RequestBuilder setHeaders(FluentCaseInsensitiveStringsMap headers) { - return super.setHeaders(headers); - } - - @Override - public RequestBuilder setHeaders(Map> headers) { - return super.setHeaders(headers); - } - - @Override - public RequestBuilder setFormParams(List params) { - return super.setFormParams(params); - } - - @Override - public RequestBuilder setFormParams(Map> params) { - return super.setFormParams(params); - } - - @Override - public RequestBuilder setMethod(String method) { - return super.setMethod(method); - } - - @Override - public RequestBuilder setUrl(String url) { - return super.setUrl(url); - } - - @Override - public RequestBuilder setProxyServer(ProxyServer proxyServer) { - return super.setProxyServer(proxyServer); - } - - @Override - public RequestBuilder setVirtualHost(String virtualHost) { - return super.setVirtualHost(virtualHost); - } - - @Override - public RequestBuilder setFollowRedirect(boolean followRedirect) { - return super.setFollowRedirect(followRedirect); - } - - @Override - public RequestBuilder addOrReplaceCookie(Cookie c) { - return super.addOrReplaceCookie(c); - } -} diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java deleted file mode 100644 index 1b1a94bad2..0000000000 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ /dev/null @@ -1,608 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.multipart.Part; -import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.util.QueryComputer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.InputStream; -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * Builder for {@link Request} - * - * @param - */ -public abstract class RequestBuilderBase> { - private final static Logger logger = LoggerFactory.getLogger(RequestBuilderBase.class); - - private static final UriComponents DEFAULT_REQUEST_URL = UriComponents.create("http://localhost/"); - - private static final class RequestImpl implements Request { - private String method; - private UriComponents uri; - private InetAddress address; - private InetAddress localAddress; - private FluentCaseInsensitiveStringsMap headers = new FluentCaseInsensitiveStringsMap(); - private ArrayList cookies; - private byte[] byteData; - private String stringData; - private InputStream streamData; - private BodyGenerator bodyGenerator; - private List formParams; - private List parts; - private String virtualHost; - private long length = -1; - public ProxyServer proxyServer; - private Realm realm; - private File file; - private Boolean followRedirect; - private int requestTimeoutInMs; - private long rangeOffset; - public String charset; - private ConnectionPoolKeyStrategy connectionPoolKeyStrategy = DefaultConnectionPoolStrategy.INSTANCE; - private List queryParams; - - public RequestImpl() { - } - - public RequestImpl(Request prototype) { - if (prototype != null) { - this.method = prototype.getMethod(); - this.uri = prototype.getURI(); - this.address = prototype.getInetAddress(); - this.localAddress = prototype.getLocalAddress(); - this.headers = new FluentCaseInsensitiveStringsMap(prototype.getHeaders()); - this.cookies = new ArrayList(prototype.getCookies()); - this.byteData = prototype.getByteData(); - this.stringData = prototype.getStringData(); - this.streamData = prototype.getStreamData(); - this.bodyGenerator = prototype.getBodyGenerator(); - this.formParams = prototype.getFormParams() == null ? null : new ArrayList(prototype.getFormParams()); - this.parts = prototype.getParts() == null ? null : new ArrayList(prototype.getParts()); - this.virtualHost = prototype.getVirtualHost(); - this.length = prototype.getContentLength(); - this.proxyServer = prototype.getProxyServer(); - this.realm = prototype.getRealm(); - this.file = prototype.getFile(); - this.followRedirect = prototype.getFollowRedirect(); - this.requestTimeoutInMs = prototype.getRequestTimeoutInMs(); - this.rangeOffset = prototype.getRangeOffset(); - this.charset = prototype.getBodyEncoding(); - this.connectionPoolKeyStrategy = prototype.getConnectionPoolKeyStrategy(); - } - } - - @Override - public String getMethod() { - return method; - } - - @Override - public InetAddress getInetAddress() { - return address; - } - - @Override - public InetAddress getLocalAddress() { - return localAddress; - } - - @Override - public UriComponents getURI() { - return uri; - } - - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return headers; - } - - @Override - public Collection getCookies() { - return cookies != null ? Collections.unmodifiableCollection(cookies) : Collections. emptyList(); - } - - @Override - public byte[] getByteData() { - return byteData; - } - - @Override - public String getStringData() { - return stringData; - } - - @Override - public InputStream getStreamData() { - return streamData; - } - - @Override - public BodyGenerator getBodyGenerator() { - return bodyGenerator; - } - - @Override - public long getContentLength() { - return length; - } - - @Override - public List getFormParams() { - return formParams != null ? formParams : Collections. emptyList(); - } - - @Override - public List getParts() { - return parts != null ? parts : Collections. emptyList(); - } - - @Override - public String getVirtualHost() { - return virtualHost; - } - - @Override - public ProxyServer getProxyServer() { - return proxyServer; - } - - @Override - public Realm getRealm() { - return realm; - } - - @Override - public File getFile() { - return file; - } - - @Override - public Boolean getFollowRedirect() { - return followRedirect; - } - - @Override - public int getRequestTimeoutInMs() { - return requestTimeoutInMs; - } - - @Override - public long getRangeOffset() { - return rangeOffset; - } - - @Override - public String getBodyEncoding() { - return charset; - } - - @Override - public ConnectionPoolKeyStrategy getConnectionPoolKeyStrategy() { - return connectionPoolKeyStrategy; - } - - @Override - public List getQueryParams() { - if (queryParams == null) - // lazy load - if (isNonEmpty(uri.getQuery())) { - queryParams = new ArrayList(1); - for (String queryStringParam : uri.getQuery().split("&")) { - int pos = queryStringParam.indexOf('='); - if (pos <= 0) - queryParams.add(new Param(queryStringParam, null)); - else - queryParams.add(new Param(queryStringParam.substring(0, pos), queryStringParam.substring(pos + 1))); - } - } else - queryParams = Collections.emptyList(); - return queryParams; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(getURI().toUrl()); - - sb.append("\t"); - sb.append(method); - sb.append("\theaders:"); - if (isNonEmpty(headers)) { - for (String name : headers.keySet()) { - sb.append("\t"); - sb.append(name); - sb.append(":"); - sb.append(headers.getJoinedValue(name, ", ")); - } - } - if (isNonEmpty(formParams)) { - sb.append("\tformParams:"); - for (Param param : formParams) { - sb.append("\t"); - sb.append(param.getName()); - sb.append(":"); - sb.append(param.getValue()); - } - } - - return sb.toString(); - } - } - - private final Class derived; - protected final RequestImpl request; - protected QueryComputer queryComputer; - protected List queryParams; - protected SignatureCalculator signatureCalculator; - - protected RequestBuilderBase(Class derived, String method, boolean disableUrlEncoding) { - this(derived, method, QueryComputer.queryComputer(disableUrlEncoding)); - } - - protected RequestBuilderBase(Class derived, String method, QueryComputer queryComputer) { - this.derived = derived; - request = new RequestImpl(); - request.method = method; - this.queryComputer = queryComputer; - } - - protected RequestBuilderBase(Class derived, Request prototype) { - this(derived, prototype, QueryComputer.URL_ENCODING_ENABLED_QUERY_COMPUTER); - } - - protected RequestBuilderBase(Class derived, Request prototype, QueryComputer queryComputer) { - this.derived = derived; - request = new RequestImpl(prototype); - this.queryComputer = queryComputer; - } - - public T setUrl(String url) { - return setURI(UriComponents.create(url)); - } - - public T setURI(UriComponents uri) { - request.uri = uri; - return derived.cast(this); - } - - public T setInetAddress(InetAddress address) { - request.address = address; - return derived.cast(this); - } - - public T setLocalInetAddress(InetAddress address) { - request.localAddress = address; - return derived.cast(this); - } - - public T setVirtualHost(String virtualHost) { - request.virtualHost = virtualHost; - return derived.cast(this); - } - - public T setHeader(String name, String value) { - request.headers.replace(name, value); - return derived.cast(this); - } - - public T addHeader(String name, String value) { - if (value == null) { - logger.warn("Value was null, set to \"\""); - value = ""; - } - - request.headers.add(name, value); - return derived.cast(this); - } - - public T setHeaders(FluentCaseInsensitiveStringsMap headers) { - request.headers = (headers == null ? new FluentCaseInsensitiveStringsMap() : new FluentCaseInsensitiveStringsMap(headers)); - return derived.cast(this); - } - - public T setHeaders(Map> headers) { - request.headers = (headers == null ? new FluentCaseInsensitiveStringsMap() : new FluentCaseInsensitiveStringsMap(headers)); - return derived.cast(this); - } - - public T setContentLength(int length) { - request.length = length; - return derived.cast(this); - } - - private void lazyInitCookies() { - if (request.cookies == null) - request.cookies = new ArrayList(3); - } - - public T setCookies(Collection cookies) { - request.cookies = new ArrayList(cookies); - return derived.cast(this); - } - - public T addCookie(Cookie cookie) { - lazyInitCookies(); - request.cookies.add(cookie); - return derived.cast(this); - } - - public T addOrReplaceCookie(Cookie cookie) { - String cookieKey = cookie.getName(); - boolean replace = false; - int index = 0; - lazyInitCookies(); - for (Cookie c : request.cookies) { - if (c.getName().equals(cookieKey)) { - replace = true; - break; - } - - index++; - } - if (replace) - request.cookies.set(index, cookie); - else - request.cookies.add(cookie); - return derived.cast(this); - } - - public void resetCookies() { - if (request.cookies != null) - request.cookies.clear(); - } - - public void resetQuery() { - queryParams = null; - request.uri = request.uri.withNewQuery(null); - } - - public void resetFormParams() { - request.formParams = null; - } - - public void resetNonMultipartData() { - request.byteData = null; - request.stringData = null; - request.streamData = null; - request.length = -1; - } - - public void resetMultipartData() { - request.parts = null; - } - - public T setBody(File file) { - request.file = file; - return derived.cast(this); - } - - public T setBody(byte[] data) { - resetFormParams(); - resetNonMultipartData(); - resetMultipartData(); - request.byteData = data; - return derived.cast(this); - } - - public T setBody(String data) { - resetFormParams(); - resetNonMultipartData(); - resetMultipartData(); - request.stringData = data; - return derived.cast(this); - } - - public T setBody(InputStream stream) { - resetFormParams(); - resetNonMultipartData(); - resetMultipartData(); - request.streamData = stream; - return derived.cast(this); - } - - public T setBody(BodyGenerator bodyGenerator) { - request.bodyGenerator = bodyGenerator; - return derived.cast(this); - } - - public T addQueryParam(String name, String value) { - if (queryParams == null) { - queryParams = new ArrayList(1); - } - queryParams.add(new Param(name, value)); - return derived.cast(this); - } - - public T addQueryParams(List queryParams) { - for (Param queryParam: queryParams) - addQueryParam(queryParam.getName(), queryParam.getValue()); - return derived.cast(this); - } - - private List map2ParamList(Map> map) { - if (map == null) - return null; - - List params = new ArrayList(map.size()); - for (Map.Entry> entries : map.entrySet()) { - String name = entries.getKey(); - for (String value : entries.getValue()) - params.add(new Param(name, value)); - } - return params; - } - - public T setQueryParams(Map> map) { - return setQueryParams(map2ParamList(map)); - } - - public T setQueryParams(List params) { - queryParams = params; - return derived.cast(this); - } - - public T addFormParam(String name, String value) { - resetNonMultipartData(); - resetMultipartData(); - if (request.formParams == null) - request.formParams = new ArrayList(1); - request.formParams.add(new Param(name, value)); - return derived.cast(this); - } - - public T setFormParams(Map> map) { - return setFormParams(map2ParamList(map)); - } - public T setFormParams(List params) { - resetNonMultipartData(); - resetMultipartData(); - request.formParams = params; - return derived.cast(this); - } - - public T addBodyPart(Part part) { - resetFormParams(); - resetNonMultipartData(); - if (request.parts == null) - request.parts = new ArrayList(); - request.parts.add(part); - return derived.cast(this); - } - - public T setProxyServer(ProxyServer proxyServer) { - request.proxyServer = proxyServer; - return derived.cast(this); - } - - public T setRealm(Realm realm) { - request.realm = realm; - return derived.cast(this); - } - - public T setFollowRedirect(boolean followRedirect) { - request.followRedirect = followRedirect; - return derived.cast(this); - } - - public T setRequestTimeoutInMs(int requestTimeoutInMs) { - request.requestTimeoutInMs = requestTimeoutInMs; - return derived.cast(this); - } - - public T setRangeOffset(long rangeOffset) { - request.rangeOffset = rangeOffset; - return derived.cast(this); - } - - public T setMethod(String method) { - request.method = method; - return derived.cast(this); - } - - public T setBodyEncoding(String charset) { - request.charset = charset; - return derived.cast(this); - } - - public T setConnectionPoolKeyStrategy(ConnectionPoolKeyStrategy connectionPoolKeyStrategy) { - request.connectionPoolKeyStrategy = connectionPoolKeyStrategy; - return derived.cast(this); - } - - public T setSignatureCalculator(SignatureCalculator signatureCalculator) { - this.signatureCalculator = signatureCalculator; - return derived.cast(this); - } - - private void executeSignatureCalculator() { - /* Let's first calculate and inject signature, before finalizing actual build - * (order does not matter with current implementation but may in future) - */ - if (signatureCalculator != null) { - signatureCalculator.calculateAndAddSignature(request, this); - } - } - - private void computeRequestCharset() { - if (request.charset == null) { - try { - final String contentType = request.headers.getFirstValue("Content-Type"); - if (contentType != null) { - final String charset = AsyncHttpProviderUtils.parseCharset(contentType); - if (charset != null) { - // ensure that if charset is provided with the Content-Type header, - // we propagate that down to the charset of the Request object - request.charset = charset; - } - } - } catch (Throwable e) { - // NoOp -- we can't fix the Content-Type or charset from here - } - } - } - - private void computeRequestLength() { - if (request.length < 0 && request.streamData == null) { - // can't concatenate content-length - final String contentLength = request.headers.getFirstValue("Content-Length"); - - if (contentLength != null) { - try { - request.length = Long.parseLong(contentLength); - } catch (NumberFormatException e) { - // NoOp -- we wdn't specify length so it will be chunked? - } - } - } - } - - private void computeFinalUri() { - - if (request.uri == null) { - logger.debug("setUrl hasn't been invoked. Using {}", DEFAULT_REQUEST_URL); - request.uri = DEFAULT_REQUEST_URL; - } - - AsyncHttpProviderUtils.validateSupportedScheme(request.uri); - - String newQuery = queryComputer.computeFullQueryString(request.uri.getQuery(), queryParams); - - request.uri = request.uri.withNewQuery(newQuery); - } - - public Request build() { - computeFinalUri(); - executeSignatureCalculator(); - computeRequestCharset(); - computeRequestLength(); - return request; - } -} - diff --git a/api/src/main/java/org/asynchttpclient/Response.java b/api/src/main/java/org/asynchttpclient/Response.java deleted file mode 100644 index 9c58ae1f49..0000000000 --- a/api/src/main/java/org/asynchttpclient/Response.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.uri.UriComponents; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * Represents the asynchronous HTTP response callback for an {@link AsyncCompletionHandler} - */ -public interface Response { - /** - * Returns the status code for the request. - * - * @return The status code - */ - int getStatusCode(); - - /** - * Returns the status text for the request. - * - * @return The status text - */ - String getStatusText(); - - /** - * Return the entire response body as a byte[]. - * - * @return the entire response body as a byte[]. - * @throws IOException - */ - byte[] getResponseBodyAsBytes() throws IOException; - - /** - * Return the entire response body as a ByteBuffer. - * - * @return the entire response body as a ByteBuffer. - * @throws IOException - */ - ByteBuffer getResponseBodyAsByteBuffer() throws IOException; - - /** - * 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. - * - * @return The input stream - * @throws java.io.IOException - */ - InputStream getResponseBodyAsStream() throws IOException; - - /** - * Returns the first maxLength bytes of the response body as a string. Note that this does not check whether the content type is actually a textual one, but it will use the - * charset if present in the content type header. - * - * @param maxLength - * The maximum number of bytes to read - * @param charset - * the charset to use when decoding the stream - * @return The response body - * @throws java.io.IOException - */ - String getResponseBodyExcerpt(int maxLength, String charset) throws IOException; - - /** - * Return the entire response body as a String. - * - * @param charset - * the charset to use when decoding the stream - * @return the entire response body as a String. - * @throws IOException - */ - String getResponseBody(String charset) throws IOException; - - /** - * Returns the first maxLength bytes of the response body as a string. Note that this does not check whether the content type is actually a textual one, but it will use the - * charset if present in the content type header. - * - * @param maxLength - * The maximum number of bytes to read - * @return The response body - * @throws java.io.IOException - */ - String getResponseBodyExcerpt(int maxLength) throws IOException; - - /** - * Return the entire response body as a String. - * - * @return the entire response body as a String. - * @throws IOException - */ - String getResponseBody() throws IOException; - - /** - * Return the request {@link UriComponents}. Note that if the request got redirected, the value of the {@link URI} will be the last valid redirect url. - * - * @return the request {@link UriComponents}. - */ - UriComponents getUri(); - - /** - * Return the content-type header value. - * - * @return the content-type header value. - */ - String getContentType(); - - /** - * Return the response header - * - * @return the response header - */ - String getHeader(String name); - - /** - * Return a {@link List} of the response header value. - * - * @return the response header - */ - List getHeaders(String name); - - FluentCaseInsensitiveStringsMap getHeaders(); - - /** - * Return true if the response redirects to another object. - * - * @return True if the response redirects to another object. - */ - boolean isRedirected(); - - /** - * Subclasses SHOULD implement toString() in a way that identifies the request for logging. - * - * @return The textual representation - */ - String toString(); - - /** - * Return the list of {@link Cookie}. - */ - List getCookies(); - - /** - * Return true if the response's status has been computed by an {@link AsyncHandler} - * - * @return true if the response's status has been computed by an {@link AsyncHandler} - */ - boolean hasResponseStatus(); - - /** - * Return true if the response's headers has been computed by an {@link AsyncHandler} It will return false if the either - * {@link AsyncHandler#onStatusReceived(HttpResponseStatus)} or {@link AsyncHandler#onHeadersReceived(HttpResponseHeaders)} returned {@link AsyncHandler.STATE#ABORT} - * - * @return true if the response's headers has been computed by an {@link AsyncHandler} - */ - boolean hasResponseHeaders(); - - /** - * Return true if the response's body has been computed by an {@link AsyncHandler}. It will return false if the either {@link AsyncHandler#onStatusReceived(HttpResponseStatus)} - * or {@link AsyncHandler#onHeadersReceived(HttpResponseHeaders)} returned {@link AsyncHandler.STATE#ABORT} - * - * @return true if the response's body has been computed by an {@link AsyncHandler} - */ - boolean hasResponseBody(); - - public static class ResponseBuilder { - private final List bodyParts = new ArrayList(); - private HttpResponseStatus status; - private HttpResponseHeaders headers; - - public ResponseBuilder accumulate(HttpResponseStatus status) { - this.status = status; - return this; - } - - public ResponseBuilder accumulate(HttpResponseHeaders headers) { - this.headers = headers; - return this; - } - - /** - * @param bodyPart - * a body part (possibly empty, but will be filtered out) - * @return this - */ - public ResponseBuilder accumulate(HttpResponseBodyPart bodyPart) { - if (bodyPart.length() > 0) - bodyParts.add(bodyPart); - return this; - } - - /** - * Build a {@link Response} instance - * - * @return a {@link Response} instance - */ - public Response build() { - return status == null ? null : status.prepareResponse(headers, bodyParts); - } - - /** - * Reset the internal state of this builder. - */ - public void reset() { - bodyParts.clear(); - status = null; - headers = null; - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/ResumableBodyConsumer.java b/api/src/main/java/org/asynchttpclient/ResumableBodyConsumer.java deleted file mode 100644 index 42e71536ce..0000000000 --- a/api/src/main/java/org/asynchttpclient/ResumableBodyConsumer.java +++ /dev/null @@ -1,36 +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; - -import java.io.IOException; - -/** - * @author Benjamin Hanzelmann - */ -public interface ResumableBodyConsumer extends BodyConsumer { - - /** - * Prepare this consumer to resume a download, for example by seeking to the end of the underlying file. - * - * @throws IOException - */ - void resume() throws IOException; - - /** - * Get the previously transferred bytes, for example the current file size. - * - * @throws IOException - */ - long getTransferredBytes() throws IOException; -} diff --git a/api/src/main/java/org/asynchttpclient/SSLEngineFactory.java b/api/src/main/java/org/asynchttpclient/SSLEngineFactory.java deleted file mode 100644 index a7152bbb4a..0000000000 --- a/api/src/main/java/org/asynchttpclient/SSLEngineFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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; - -import javax.net.ssl.SSLEngine; - -import java.security.GeneralSecurityException; - -/** - * Factory that creates an {@link SSLEngine} to be used for a single SSL connection. - */ -public interface SSLEngineFactory { - /** - * Creates new {@link SSLEngine}. - * - * @return new engine - * @throws GeneralSecurityException if the SSLEngine cannot be created - */ - SSLEngine newSSLEngine() throws GeneralSecurityException; -} diff --git a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java deleted file mode 100644 index e90cee8f6b..0000000000 --- a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java +++ /dev/null @@ -1,866 +0,0 @@ -/* - * Copyright (c) 2010 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; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; - -import java.io.Closeable; -import java.io.IOException; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; - -import javax.net.ssl.SSLContext; - -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.multipart.Part; -import org.asynchttpclient.resumable.ResumableAsyncHandler; -import org.asynchttpclient.resumable.ResumableIOExceptionFilter; -import org.asynchttpclient.simple.HeaderMap; -import org.asynchttpclient.simple.SimpleAHCTransferListener; -import org.asynchttpclient.uri.UriComponents; - -/** - * Simple implementation of {@link AsyncHttpClient} and it's related builders ({@link AsyncHttpClientConfig}, - * {@link Realm}, {@link ProxyServer} and {@link AsyncHandler}. You can - * build powerful application by just using this class. - *

- * This class rely on {@link BodyGenerator} and {@link BodyConsumer} for handling the request and response body. No - * {@link AsyncHandler} are required. As simple as: - *

- * SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder()
- * .setIdleConnectionInPoolTimeout(100)
- * .setMaximumConnectionsTotal(50)
- * .setRequestTimeout(5 * 60 * 1000)
- * .setUrl(getTargetUrl())
- * .setHeader("Content-Type", "text/html").build();
- * 

- * StringBuilder s = new StringBuilder(); - * Future future = client.post(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes())), new AppendableBodyConsumer(s)); - *

- * or - *
- * public void ByteArrayOutputStreamBodyConsumerTest() throws Throwable {
- * 

- * SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder() - * .setUrl(getTargetUrl()) - * .build(); - *

- * ByteArrayOutputStream o = new ByteArrayOutputStream(10); - * Future future = client.post(new FileodyGenerator(myFile), new OutputStreamBodyConsumer(o)); - *

- */ -public class SimpleAsyncHttpClient implements Closeable { - - private final AsyncHttpClientConfig config; - private final RequestBuilder requestBuilder; - private AsyncHttpClient asyncHttpClient; - private final ThrowableHandler defaultThrowableHandler; - private final boolean resumeEnabled; - private final ErrorDocumentBehaviour errorDocumentBehaviour; - private final SimpleAHCTransferListener listener; - private final boolean derived; - private final String providerClass; - - private SimpleAsyncHttpClient(AsyncHttpClientConfig config, RequestBuilder requestBuilder, ThrowableHandler defaultThrowableHandler, - ErrorDocumentBehaviour errorDocumentBehaviour, boolean resumeEnabled, AsyncHttpClient ahc, SimpleAHCTransferListener listener, - String providerClass) { - this.config = config; - this.requestBuilder = requestBuilder; - this.defaultThrowableHandler = defaultThrowableHandler; - this.resumeEnabled = resumeEnabled; - this.errorDocumentBehaviour = errorDocumentBehaviour; - this.asyncHttpClient = ahc; - this.listener = listener; - this.providerClass = providerClass; - - this.derived = ahc != null; - } - - public Future post(Part... parts) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("POST"); - - for (Part part : parts) { - r.addBodyPart(part); - } - - return execute(r, null, null); - } - - public Future post(BodyConsumer consumer, Part... parts) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("POST"); - - for (Part part : parts) { - r.addBodyPart(part); - } - - return execute(r, consumer, null); - } - - public Future post(BodyGenerator bodyGenerator) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("POST"); - r.setBody(bodyGenerator); - return execute(r, null, null); - } - - public Future post(BodyGenerator bodyGenerator, ThrowableHandler throwableHandler) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("POST"); - r.setBody(bodyGenerator); - return execute(r, null, throwableHandler); - } - - public Future post(BodyGenerator bodyGenerator, BodyConsumer bodyConsumer) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("POST"); - r.setBody(bodyGenerator); - return execute(r, bodyConsumer, null); - } - - public Future post(BodyGenerator bodyGenerator, BodyConsumer bodyConsumer, ThrowableHandler throwableHandler) - throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("POST"); - r.setBody(bodyGenerator); - return execute(r, bodyConsumer, throwableHandler); - } - - public Future put(Part... parts) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("POST"); - - for (Part part : parts) { - r.addBodyPart(part); - } - - return execute(r, null, null); - } - - public Future put(BodyConsumer consumer, Part... parts) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("POST"); - - for (Part part : parts) { - r.addBodyPart(part); - } - - return execute(r, consumer, null); - } - - public Future put(BodyGenerator bodyGenerator, BodyConsumer bodyConsumer) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("PUT"); - r.setBody(bodyGenerator); - return execute(r, bodyConsumer, null); - } - - public Future put(BodyGenerator bodyGenerator, BodyConsumer bodyConsumer, ThrowableHandler throwableHandler) - throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("PUT"); - r.setBody(bodyGenerator); - return execute(r, bodyConsumer, throwableHandler); - } - - public Future put(BodyGenerator bodyGenerator) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("PUT"); - r.setBody(bodyGenerator); - return execute(r, null, null); - } - - public Future put(BodyGenerator bodyGenerator, ThrowableHandler throwableHandler) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("PUT"); - r.setBody(bodyGenerator); - return execute(r, null, throwableHandler); - } - - public Future get() throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - return execute(r, null, null); - } - - public Future get(ThrowableHandler throwableHandler) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - return execute(r, null, throwableHandler); - } - - public Future get(BodyConsumer bodyConsumer) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - return execute(r, bodyConsumer, null); - } - - public Future get(BodyConsumer bodyConsumer, ThrowableHandler throwableHandler) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - return execute(r, bodyConsumer, throwableHandler); - } - - public Future delete() throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("DELETE"); - return execute(r, null, null); - } - - public Future delete(ThrowableHandler throwableHandler) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("DELETE"); - return execute(r, null, throwableHandler); - } - - public Future delete(BodyConsumer bodyConsumer) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("DELETE"); - return execute(r, bodyConsumer, null); - } - - public Future delete(BodyConsumer bodyConsumer, ThrowableHandler throwableHandler) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("DELETE"); - return execute(r, bodyConsumer, throwableHandler); - } - - public Future head() throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("HEAD"); - return execute(r, null, null); - } - - public Future head(ThrowableHandler throwableHandler) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("HEAD"); - return execute(r, null, throwableHandler); - } - - public Future options() throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("OPTIONS"); - return execute(r, null, null); - } - - public Future options(ThrowableHandler throwableHandler) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("OPTIONS"); - return execute(r, null, throwableHandler); - } - - public Future options(BodyConsumer bodyConsumer) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("OPTIONS"); - return execute(r, bodyConsumer, null); - } - - public Future options(BodyConsumer bodyConsumer, ThrowableHandler throwableHandler) throws IOException { - RequestBuilder r = rebuildRequest(requestBuilder.build()); - r.setMethod("OPTIONS"); - return execute(r, bodyConsumer, throwableHandler); - } - - private RequestBuilder rebuildRequest(Request rb) { - return new RequestBuilder(rb); - } - - private Future execute(RequestBuilder rb, BodyConsumer bodyConsumer, ThrowableHandler throwableHandler) throws IOException { - if (throwableHandler == null) { - throwableHandler = defaultThrowableHandler; - } - - Request request = rb.build(); - ProgressAsyncHandler handler = new BodyConsumerAsyncHandler(bodyConsumer, throwableHandler, errorDocumentBehaviour, - request.getURI(), listener); - - if (resumeEnabled && request.getMethod().equals("GET") && bodyConsumer != null && bodyConsumer instanceof ResumableBodyConsumer) { - ResumableBodyConsumer fileBodyConsumer = (ResumableBodyConsumer) bodyConsumer; - long length = fileBodyConsumer.getTransferredBytes(); - fileBodyConsumer.resume(); - handler = new ResumableBodyConsumerAsyncHandler(length, handler); - } - - return asyncHttpClient().executeRequest(request, handler); - } - - private AsyncHttpClient asyncHttpClient() { - synchronized (config) { - if (asyncHttpClient == null) { - if (providerClass == null) - asyncHttpClient = new DefaultAsyncHttpClient(config); - else - asyncHttpClient = new DefaultAsyncHttpClient(providerClass, config); - } - } - return asyncHttpClient; - } - - /** - * Close the underlying AsyncHttpClient for this instance. - *

- * If this instance is derived from another instance, this method does - * nothing as the client instance is managed by the original - * SimpleAsyncHttpClient. - * - * @see #derive() - * @see AsyncHttpClient#close() - */ - public void close() { - if (!derived && asyncHttpClient != null) { - asyncHttpClient.close(); - } - } - - /** - * Returns a Builder for a derived SimpleAsyncHttpClient that uses the same - * instance of {@link AsyncHttpClient} to execute requests. - *

- *

- *

- * The original SimpleAsyncHttpClient is responsible for managing the - * underlying AsyncHttpClient. For the derived instance, {@link #close()} is - * a NOOP. If the original SimpleAsyncHttpClient is closed, all derived - * instances become invalid. - * - * @return a Builder for a derived SimpleAsyncHttpClient that uses the same - * instance of {@link AsyncHttpClient} to execute requests, never - * {@code null}. - */ - public DerivedBuilder derive() { - return new Builder(this); - } - - public enum ErrorDocumentBehaviour { - /** - * Write error documents as usual via - * {@link BodyConsumer#consume(java.nio.ByteBuffer)}. - */ - WRITE, - - /** - * Accumulate error documents in memory but do not consume. - */ - ACCUMULATE, - - /** - * Omit error documents. An error document will neither be available in - * the response nor written via a {@link BodyConsumer}. - */ - OMIT; - } - - /** - * This interface contains possible configuration changes for a derived SimpleAsyncHttpClient. - * - * @see SimpleAsyncHttpClient#derive() - */ - /** - * This interface contains possible configuration changes for a derived SimpleAsyncHttpClient. - * - * @see SimpleAsyncHttpClient#derive() - */ - public interface DerivedBuilder { - - DerivedBuilder setFollowRedirect(boolean followRedirect); - - DerivedBuilder setVirtualHost(String virtualHost); - - DerivedBuilder setUrl(String url); - - DerivedBuilder setFormParams(List params); - - DerivedBuilder setFormParams(Map> params); - - DerivedBuilder setHeaders(Map> headers); - - DerivedBuilder setHeaders(FluentCaseInsensitiveStringsMap headers); - - DerivedBuilder setHeader(String name, String value); - - DerivedBuilder addQueryParam(String name, String value); - - DerivedBuilder addFormParam(String key, String value); - - DerivedBuilder addHeader(String name, String value); - - DerivedBuilder addCookie(Cookie cookie); - - DerivedBuilder addBodyPart(Part part); - - DerivedBuilder setResumableDownload(boolean resume); - - SimpleAsyncHttpClient build(); - } - - public final static class Builder implements DerivedBuilder { - - private final RequestBuilder requestBuilder; - private final AsyncHttpClientConfig.Builder configBuilder = new AsyncHttpClientConfig.Builder(); - private Realm.RealmBuilder realmBuilder = null; - private ProxyServer.Protocol proxyProtocol = null; - private String proxyHost = null; - private String proxyPrincipal = null; - private String proxyPassword = null; - private int proxyPort = 80; - private ThrowableHandler defaultThrowableHandler = null; - private boolean enableResumableDownload = false; - private ErrorDocumentBehaviour errorDocumentBehaviour = ErrorDocumentBehaviour.WRITE; - private AsyncHttpClient ahc = null; - private SimpleAHCTransferListener listener = null; - private String providerClass = null; - - public Builder() { - requestBuilder = new RequestBuilder("GET", false); - } - - private Builder(SimpleAsyncHttpClient client) { - this.requestBuilder = new RequestBuilder(client.requestBuilder.build()); - this.defaultThrowableHandler = client.defaultThrowableHandler; - this.errorDocumentBehaviour = client.errorDocumentBehaviour; - this.enableResumableDownload = client.resumeEnabled; - this.ahc = client.asyncHttpClient(); - this.listener = client.listener; - } - - public Builder addBodyPart(Part part) { - requestBuilder.addBodyPart(part); - return this; - } - - public Builder addCookie(Cookie cookie) { - requestBuilder.addCookie(cookie); - return this; - } - - public Builder addHeader(String name, String value) { - requestBuilder.addHeader(name, value); - return this; - } - - public Builder addFormParam(String key, String value) { - requestBuilder.addFormParam(key, value); - return this; - } - - public Builder addQueryParam(String name, String value) { - requestBuilder.addQueryParam(name, value); - return this; - } - - public Builder setHeader(String name, String value) { - requestBuilder.setHeader(name, value); - return this; - } - - public Builder setHeaders(FluentCaseInsensitiveStringsMap headers) { - requestBuilder.setHeaders(headers); - return this; - } - - public Builder setHeaders(Map> headers) { - requestBuilder.setHeaders(headers); - return this; - } - - public Builder setFormParams(Map> parameters) { - requestBuilder.setFormParams(parameters); - return this; - } - - public Builder setFormParams(List params) { - requestBuilder.setFormParams(params); - return this; - } - - public Builder setUrl(String url) { - requestBuilder.setUrl(url); - return this; - } - - public Builder setVirtualHost(String virtualHost) { - requestBuilder.setVirtualHost(virtualHost); - return this; - } - - public Builder setFollowRedirect(boolean followRedirect) { - requestBuilder.setFollowRedirect(followRedirect); - return this; - } - - public Builder setMaxConnections(int defaultMaxConnections) { - configBuilder.setMaxConnections(defaultMaxConnections); - return this; - } - - public Builder setMaxConnectionsPerHost(int defaultMaxConnectionsPerHost) { - configBuilder.setMaxConnectionsPerHost(defaultMaxConnectionsPerHost); - return this; - } - - public Builder setConnectionTimeout(int connectionTimeuot) { - configBuilder.setConnectionTimeout(connectionTimeuot); - return this; - } - - public Builder setPooledConnectionIdleTimeout(int pooledConnectionIdleTimeout) { - configBuilder.setPooledConnectionIdleTimeout(pooledConnectionIdleTimeout); - return this; - } - - public Builder setRequestTimeout(int defaultRequestTimeout) { - configBuilder.setRequestTimeout(defaultRequestTimeout); - return this; - } - - public Builder setMaxRedirects(int maxRedirects) { - configBuilder.setMaxRedirects(maxRedirects); - return this; - } - - public Builder setCompressionEnabled(boolean compressionEnabled) { - configBuilder.setCompressionEnabled(compressionEnabled); - return this; - } - - public Builder setUserAgent(String userAgent) { - configBuilder.setUserAgent(userAgent); - return this; - } - - public Builder setAllowPoolingConnections(boolean allowPoolingConnections) { - configBuilder.setAllowPoolingConnections(allowPoolingConnections); - return this; - } - - public Builder setExecutorService(ExecutorService applicationThreadPool) { - configBuilder.setExecutorService(applicationThreadPool); - return this; - } - - public Builder setSSLContext(final SSLContext sslContext) { - configBuilder.setSSLContext(sslContext); - return this; - } - - public Builder setRealmNtlmDomain(String domain) { - realm().setNtlmDomain(domain); - return this; - } - - public Builder setRealmPrincipal(String principal) { - realm().setPrincipal(principal); - return this; - } - - public Builder setRealmPassword(String password) { - realm().setPassword(password); - return this; - } - - public Builder setRealmScheme(Realm.AuthScheme scheme) { - realm().setScheme(scheme); - return this; - } - - public Builder setRealmName(String realmName) { - realm().setRealmName(realmName); - return this; - } - - public Builder setRealmUsePreemptiveAuth(boolean usePreemptiveAuth) { - realm().setUsePreemptiveAuth(usePreemptiveAuth); - return this; - } - - public Builder setRealmEnconding(String enc) { - realm().setEncoding(enc); - return this; - } - - public Builder setProxyProtocol(ProxyServer.Protocol protocol) { - this.proxyProtocol = protocol; - return this; - } - - public Builder setProxyHost(String host) { - this.proxyHost = host; - return this; - } - - public Builder setProxyPrincipal(String principal) { - this.proxyPrincipal = principal; - return this; - } - - public Builder setProxyPassword(String password) { - this.proxyPassword = password; - return this; - } - - public Builder setProxyPort(int port) { - this.proxyPort = port; - return this; - } - - public Builder setDefaultThrowableHandler(ThrowableHandler throwableHandler) { - this.defaultThrowableHandler = throwableHandler; - return this; - } - - /** - * This setting controls whether an error document should be written via - * the {@link BodyConsumer} after an error status code was received (e.g. - * 404). Default is {@link ErrorDocumentBehaviour#WRITE}. - */ - public Builder setErrorDocumentBehaviour(ErrorDocumentBehaviour behaviour) { - this.errorDocumentBehaviour = behaviour; - return this; - } - - /** - * Enable resumable downloads for the SimpleAHC. Resuming downloads will only work for GET requests - * with an instance of {@link ResumableBodyConsumer}. - */ - public Builder setResumableDownload(boolean enableResumableDownload) { - this.enableResumableDownload = enableResumableDownload; - return this; - } - - private Realm.RealmBuilder realm() { - if (realmBuilder == null) { - realmBuilder = new Realm.RealmBuilder(); - } - return realmBuilder; - } - - /** - * Set the listener to notify about connection progress. - */ - public Builder setListener(SimpleAHCTransferListener listener) { - this.listener = listener; - return this; - } - - /** - * Set the number of time a request will be retried when an {@link java.io.IOException} occurs because of a Network exception. - * - * @param maxRequestRetry the number of time a request will be retried - * @return this - */ - public Builder setMaxRequestRetry(int maxRequestRetry) { - configBuilder.setMaxRequestRetry(maxRequestRetry); - return this; - } - - public Builder setProviderClass(String providerClass) { - this.providerClass = providerClass; - return this; - } - - public Builder setAcceptAnyCertificate(boolean acceptAnyCertificate) { - configBuilder.setAcceptAnyCertificate(acceptAnyCertificate); - return this; - } - - public SimpleAsyncHttpClient build() { - - if (realmBuilder != null) { - configBuilder.setRealm(realmBuilder.build()); - } - - if (proxyHost != null) { - configBuilder.setProxyServer(new ProxyServer(proxyProtocol, proxyHost, proxyPort, proxyPrincipal, proxyPassword)); - } - - configBuilder.addIOExceptionFilter(new ResumableIOExceptionFilter()); - - SimpleAsyncHttpClient sc = new SimpleAsyncHttpClient(configBuilder.build(), requestBuilder, defaultThrowableHandler, - errorDocumentBehaviour, enableResumableDownload, ahc, listener, providerClass); - - return sc; - } - } - - private final static class ResumableBodyConsumerAsyncHandler extends ResumableAsyncHandler implements ProgressAsyncHandler { - - private final ProgressAsyncHandler delegate; - - public ResumableBodyConsumerAsyncHandler(long byteTransferred, ProgressAsyncHandler delegate) { - super(byteTransferred, delegate); - this.delegate = delegate; - } - - public AsyncHandler.STATE onHeaderWriteCompleted() { - return delegate.onHeaderWriteCompleted(); - } - - public AsyncHandler.STATE onContentWriteCompleted() { - return delegate.onContentWriteCompleted(); - } - - public AsyncHandler.STATE onContentWriteProgress(long amount, long current, long total) { - return delegate.onContentWriteProgress(amount, current, total); - } - } - - private final static class BodyConsumerAsyncHandler extends AsyncCompletionHandlerBase { - - private final BodyConsumer bodyConsumer; - private final ThrowableHandler exceptionHandler; - private final ErrorDocumentBehaviour errorDocumentBehaviour; - private final UriComponents uri; - private final SimpleAHCTransferListener listener; - - private boolean accumulateBody = false; - private boolean omitBody = false; - private int amount = 0; - private long total = -1; - - public BodyConsumerAsyncHandler(BodyConsumer bodyConsumer, ThrowableHandler exceptionHandler, - ErrorDocumentBehaviour errorDocumentBehaviour, UriComponents uri, SimpleAHCTransferListener listener) { - this.bodyConsumer = bodyConsumer; - this.exceptionHandler = exceptionHandler; - this.errorDocumentBehaviour = errorDocumentBehaviour; - this.uri = uri; - this.listener = listener; - } - - @Override - public void onThrowable(Throwable t) { - try { - if (exceptionHandler != null) { - exceptionHandler.onThrowable(t); - } else { - super.onThrowable(t); - } - } finally { - closeConsumer(); - } - } - - /** - * {@inheritDoc} - */ - public STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { - fireReceived(content); - if (omitBody) { - return STATE.CONTINUE; - } - - if (!accumulateBody && bodyConsumer != null) { - bodyConsumer.consume(content.getBodyByteBuffer()); - } else { - return super.onBodyPartReceived(content); - } - return STATE.CONTINUE; - } - - /** - * {@inheritDoc} - */ - @Override - public Response onCompleted(Response response) throws Exception { - fireCompleted(response); - closeConsumer(); - return super.onCompleted(response); - } - - private void closeConsumer() { - if (bodyConsumer != null) - closeSilently(bodyConsumer); - } - - @Override - public STATE onStatusReceived(HttpResponseStatus status) throws Exception { - fireStatus(status); - - if (isErrorStatus(status)) { - switch (errorDocumentBehaviour) { - case ACCUMULATE: - accumulateBody = true; - break; - case OMIT: - omitBody = true; - break; - default: - break; - } - } - return super.onStatusReceived(status); - } - - private boolean isErrorStatus(HttpResponseStatus status) { - return status.getStatusCode() >= 400; - } - - @Override - public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { - calculateTotal(headers); - - fireHeaders(headers); - - return super.onHeadersReceived(headers); - } - - private void calculateTotal(HttpResponseHeaders headers) { - String length = headers.getHeaders().getFirstValue("Content-Length"); - - try { - total = Integer.valueOf(length); - } catch (Exception e) { - total = -1; - } - } - - @Override - public STATE onContentWriteProgress(long amount, long current, long total) { - fireSent(uri, amount, current, total); - return super.onContentWriteProgress(amount, current, total); - } - - private void fireStatus(HttpResponseStatus status) { - if (listener != null) { - listener.onStatus(uri, status.getStatusCode(), status.getStatusText()); - } - } - - private void fireReceived(HttpResponseBodyPart content) { - int remaining = content.getBodyByteBuffer().remaining(); - - amount += remaining; - - if (listener != null) { - listener.onBytesReceived(uri, amount, remaining, total); - } - } - - private void fireHeaders(HttpResponseHeaders headers) { - if (listener != null) { - listener.onHeaders(uri, new HeaderMap(headers.getHeaders())); - } - } - - private void fireSent(UriComponents uri, long amount, long current, long total) { - if (listener != null) { - listener.onBytesSent(uri, amount, current, total); - } - } - - private void fireCompleted(Response response) { - if (listener != null) { - listener.onCompleted(uri, response.getStatusCode(), response.getStatusText()); - } - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/ThrowableHandler.java b/api/src/main/java/org/asynchttpclient/ThrowableHandler.java deleted file mode 100644 index 1bc28ea824..0000000000 --- a/api/src/main/java/org/asynchttpclient/ThrowableHandler.java +++ /dev/null @@ -1,22 +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; - -/** - * Simple {@link Throwable} handler to be used with {@link SimpleAsyncHttpClient} - */ -public interface ThrowableHandler { - - void onThrowable(Throwable t); -} diff --git a/api/src/main/java/org/asynchttpclient/UpgradeHandler.java b/api/src/main/java/org/asynchttpclient/UpgradeHandler.java deleted file mode 100644 index e541e67459..0000000000 --- a/api/src/main/java/org/asynchttpclient/UpgradeHandler.java +++ /dev/null @@ -1,36 +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; - -/** - * Invoked when an {@link AsyncHandler.STATE#UPGRADE} is returned. Currently the library only support {@link org.asynchttpclient.websocket.WebSocket} - * as type. - * - * @param - */ -public interface UpgradeHandler { - - /** - * If the HTTP Upgrade succeed (response's status code equals 101), the {@link AsyncHttpProvider} will invoke that - * method - * - * @param t an Upgradable entity - */ - void onSuccess(T t); - - /** - * If the upgrade fail. - * @param t a {@link Throwable} - */ - void onFailure(Throwable t); -} diff --git a/api/src/main/java/org/asynchttpclient/consumers/AppendableBodyConsumer.java b/api/src/main/java/org/asynchttpclient/consumers/AppendableBodyConsumer.java deleted file mode 100644 index 6f84d57f6b..0000000000 --- a/api/src/main/java/org/asynchttpclient/consumers/AppendableBodyConsumer.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2010-2013 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.consumers; - -import org.asynchttpclient.BodyConsumer; -import org.asynchttpclient.util.StandardCharsets; - -import java.io.Closeable; -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * An {@link Appendable} customer for {@link ByteBuffer} - */ -public class AppendableBodyConsumer implements BodyConsumer { - - private final Appendable appendable; - private final String encoding; - - public AppendableBodyConsumer(Appendable appendable, String encoding) { - this.appendable = appendable; - this.encoding = encoding; - } - - public AppendableBodyConsumer(Appendable appendable) { - this.appendable = appendable; - this.encoding = StandardCharsets.UTF_8.name(); - } - - @Override - public void consume(ByteBuffer byteBuffer) throws IOException { - appendable - .append(new String(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), byteBuffer.remaining(), encoding)); - } - - @Override - public void close() throws IOException { - if (appendable instanceof Closeable) { - Closeable.class.cast(appendable).close(); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/consumers/ByteBufferBodyConsumer.java b/api/src/main/java/org/asynchttpclient/consumers/ByteBufferBodyConsumer.java deleted file mode 100644 index a8b4c748e9..0000000000 --- a/api/src/main/java/org/asynchttpclient/consumers/ByteBufferBodyConsumer.java +++ /dev/null @@ -1,46 +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.consumers; - -import org.asynchttpclient.BodyConsumer; - -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * A {@link ByteBuffer} implementation of {@link BodyConsumer} - */ -public class ByteBufferBodyConsumer implements BodyConsumer { - - private final ByteBuffer byteBuffer; - - public ByteBufferBodyConsumer(ByteBuffer byteBuffer) { - this.byteBuffer = byteBuffer; - } - - /** - * {@inheritDoc} - */ - @Override - public void consume(ByteBuffer byteBuffer) throws IOException { - byteBuffer.put(byteBuffer); - } - - /** - * {@inheritDoc} - */ - @Override - public void close() throws IOException { - byteBuffer.flip(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/consumers/FileBodyConsumer.java b/api/src/main/java/org/asynchttpclient/consumers/FileBodyConsumer.java deleted file mode 100644 index 5677dfdd19..0000000000 --- a/api/src/main/java/org/asynchttpclient/consumers/FileBodyConsumer.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2010-2013 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.consumers; - -import org.asynchttpclient.ResumableBodyConsumer; - -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; - -/** - * A {@link RandomAccessFile} that can be used as a {@link ResumableBodyConsumer} - */ -public class FileBodyConsumer implements ResumableBodyConsumer { - - private final RandomAccessFile file; - - public FileBodyConsumer(RandomAccessFile file) { - this.file = file; - } - - /** - * {@inheritDoc} - */ - @Override - public void consume(ByteBuffer byteBuffer) throws IOException { - // TODO: Channel.transferFrom may be a good idea to investigate. - file.write(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), byteBuffer.remaining()); - } - - /** - * {@inheritDoc} - */ - @Override - public void close() throws IOException { - file.close(); - } - - /** - * {@inheritDoc} - */ - @Override - public long getTransferredBytes() throws IOException { - return file.length(); - } - - /** - * {@inheritDoc} - */ - @Override - public void resume() throws IOException { - file.seek(getTransferredBytes()); - } -} diff --git a/api/src/main/java/org/asynchttpclient/consumers/OutputStreamBodyConsumer.java b/api/src/main/java/org/asynchttpclient/consumers/OutputStreamBodyConsumer.java deleted file mode 100644 index 65f8f85cc9..0000000000 --- a/api/src/main/java/org/asynchttpclient/consumers/OutputStreamBodyConsumer.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2010-2013 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.consumers; - -import org.asynchttpclient.BodyConsumer; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * A simple {@link OutputStream} implementation for {@link BodyConsumer} - */ -public class OutputStreamBodyConsumer implements BodyConsumer { - - private final OutputStream outputStream; - - public OutputStreamBodyConsumer(OutputStream outputStream) { - this.outputStream = outputStream; - } - - /** - * {@inheritDoc} - */ - @Override - public void consume(ByteBuffer byteBuffer) throws IOException { - outputStream.write(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), byteBuffer.remaining()); - } - - /** - * {@inheritDoc} - */ - @Override - public void close() throws IOException { - outputStream.close(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/cookie/Cookie.java b/api/src/main/java/org/asynchttpclient/cookie/Cookie.java deleted file mode 100644 index 66c366cfaf..0000000000 --- a/api/src/main/java/org/asynchttpclient/cookie/Cookie.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.cookie; - -public class Cookie { - - public static Cookie newValidCookie(String name, String value, String rawValue, String domain, String path, long expires, int maxAge, - boolean secure, boolean httpOnly) { - - if (name == null) { - throw new NullPointerException("name"); - } - name = name.trim(); - if (name.isEmpty()) { - throw new IllegalArgumentException("empty name"); - } - - for (int i = 0; i < name.length(); i++) { - char c = name.charAt(i); - if (c > 127) { - throw new IllegalArgumentException("name contains non-ascii character: " + name); - } - - // Check prohibited characters. - switch (c) { - case '\t': - case '\n': - case 0x0b: - case '\f': - case '\r': - case ' ': - case ',': - case ';': - case '=': - throw new IllegalArgumentException("name contains one of the following prohibited characters: " + "=,; \\t\\r\\n\\v\\f: " - + name); - } - } - - if (name.charAt(0) == '$') { - throw new IllegalArgumentException("name starting with '$' not allowed: " + name); - } - - if (value == null) { - throw new NullPointerException("value"); - } - - domain = validateValue("domain", domain); - path = validateValue("path", path); - - return new Cookie(name, value, rawValue, domain, path, expires, maxAge, secure, httpOnly); - } - - private static String validateValue(String name, String value) { - if (value == null) { - return null; - } - value = value.trim(); - if (value.isEmpty()) { - return null; - } - - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - switch (c) { - case '\r': - case '\n': - case '\f': - case 0x0b: - case ';': - throw new IllegalArgumentException(name + " contains one of the following prohibited characters: " + ";\\r\\n\\f\\v (" - + value + ')'); - } - } - return value; - } - - private final String name; - private final String value; - private final String rawValue; - private final String domain; - private final String path; - private long expires; - private final int maxAge; - private final boolean secure; - private final boolean httpOnly; - - public Cookie(String name, String value, String rawValue, String domain, String path, long expires, int maxAge, boolean secure, - boolean httpOnly) { - this.name = name; - this.value = value; - this.rawValue = rawValue; - this.domain = domain; - this.path = path; - this.expires = expires; - this.maxAge = maxAge; - this.secure = secure; - this.httpOnly = httpOnly; - } - - public String getDomain() { - return domain; - } - - public String getName() { - return name; - } - - public String getValue() { - return value; - } - - public String getRawValue() { - return rawValue; - } - - public String getPath() { - return path; - } - - public long getExpires() { - return expires; - } - - public int getMaxAge() { - return maxAge; - } - - public boolean isSecure() { - return secure; - } - - public boolean isHttpOnly() { - return httpOnly; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append(name); - buf.append("="); - buf.append(rawValue); - if (domain != null) { - buf.append("; domain="); - buf.append(domain); - } - if (path != null) { - buf.append("; path="); - buf.append(path); - } - if (expires >= 0) { - buf.append("; expires="); - buf.append(expires); - } - if (maxAge >= 0) { - buf.append("; maxAge="); - buf.append(maxAge); - buf.append("s"); - } - if (secure) { - buf.append("; secure"); - } - if (httpOnly) { - buf.append("; HTTPOnly"); - } - return buf.toString(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/cookie/CookieDecoder.java b/api/src/main/java/org/asynchttpclient/cookie/CookieDecoder.java deleted file mode 100644 index 949fb069ca..0000000000 --- a/api/src/main/java/org/asynchttpclient/cookie/CookieDecoder.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.cookie; - -import org.asynchttpclient.date.CalendarTimeConverter; -import org.asynchttpclient.date.TimeConverter; - -public class CookieDecoder { - - public static final TimeConverter DEFAULT_TIME_CONVERTER = new CalendarTimeConverter(); - - public static Cookie decode(String header) { - return decode(header, DEFAULT_TIME_CONVERTER); - } - - /** - * Decodes the specified HTTP header value into {@link Cookie}. - * - * @return the decoded {@link Cookie} - */ - public static Cookie decode(String header, TimeConverter timeConverter) { - - if (timeConverter == null) - timeConverter = DEFAULT_TIME_CONVERTER; - - if (header.isEmpty()) - return null; - - KeyValuePairsParser pairsParser = new KeyValuePairsParser(timeConverter); - - final int headerLen = header.length(); - loop: for (int i = 0;;) { - - // Skip spaces and separators. - for (;;) { - if (i == headerLen) { - break loop; - } - char c = header.charAt(i); - if (c == ',') { - // Having multiple cookies in a single Set-Cookie header is - // deprecated, modern browsers only parse the first one - break loop; - - } else if (c == '\t' || c == '\n' || c == 0x0b || c == '\f' || c == '\r' || c == ' ' || c == ';') { - i++; - continue; - } - break; - } - - int newNameStart = i; - int newNameEnd = i; - String value; - String rawValue; - boolean first = true; - - if (i == headerLen) { - value = rawValue = null; - } else { - keyValLoop: for (;;) { - - char curChar = header.charAt(i); - if (curChar == ';') { - // NAME; (no value till ';') - newNameEnd = i; - value = rawValue = null; - first = false; - break keyValLoop; - } else if (curChar == '=') { - // NAME=VALUE - newNameEnd = i; - i++; - if (i == headerLen) { - // NAME= (empty value, i.e. nothing after '=') - value = rawValue = ""; - first = false; - break keyValLoop; - } - - int newValueStart = i; - char c = header.charAt(i); - if (c == '"' || c == '\'') { - // NAME="VALUE" or NAME='VALUE' - StringBuilder newValueBuf = new StringBuilder(header.length() - i); - - int rawValueStart = i; - int rawValueEnd = i; - - final char q = c; - boolean hadBackslash = false; - i++; - for (;;) { - if (i == headerLen) { - value = newValueBuf.toString(); - // only need to compute raw value for cookie - // value which is at most in 2nd position - rawValue = first ? header.substring(rawValueStart, rawValueEnd) : null; - first = false; - break keyValLoop; - } - if (hadBackslash) { - hadBackslash = false; - c = header.charAt(i++); - rawValueEnd = i; - switch (c) { - case '\\': - case '"': - case '\'': - // Escape last backslash. - newValueBuf.setCharAt(newValueBuf.length() - 1, c); - break; - default: - // Do not escape last backslash. - newValueBuf.append(c); - } - } else { - c = header.charAt(i++); - rawValueEnd = i; - if (c == q) { - value = newValueBuf.toString(); - // only need to compute raw value for - // cookie value which is at most in 2nd - // position - rawValue = first ? header.substring(rawValueStart, rawValueEnd) : null; - first = false; - break keyValLoop; - } - newValueBuf.append(c); - if (c == '\\') { - hadBackslash = true; - } - } - } - } else { - // NAME=VALUE; - int semiPos = header.indexOf(';', i); - if (semiPos > 0) { - value = rawValue = header.substring(newValueStart, semiPos); - i = semiPos; - } else { - value = rawValue = header.substring(newValueStart); - i = headerLen; - } - } - break keyValLoop; - } else { - i++; - } - - if (i == headerLen) { - // NAME (no value till the end of string) - newNameEnd = headerLen; - first = false; - value = rawValue = null; - break; - } - } - } - - pairsParser.parseKeyValuePair(header, newNameStart, newNameEnd, value, rawValue); - } - return pairsParser.cookie(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/cookie/CookieEncoder.java b/api/src/main/java/org/asynchttpclient/cookie/CookieEncoder.java deleted file mode 100644 index bf895f3a46..0000000000 --- a/api/src/main/java/org/asynchttpclient/cookie/CookieEncoder.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.cookie; - -import java.util.Collection; - -public final class CookieEncoder { - - private CookieEncoder() { - } - - public static String encode(Collection cookies) { - StringBuilder sb = new StringBuilder(); - - for (Cookie cookie : cookies) { - add(sb, cookie.getName(), cookie.getRawValue()); - } - - if (sb.length() > 0) { - sb.setLength(sb.length() - 2); - } - return sb.toString(); - } - - private static void add(StringBuilder sb, String name, String val) { - - if (val == null) { - val = ""; - } - - sb.append(name); - sb.append('='); - sb.append(val); - sb.append(';'); - sb.append(' '); - } -} diff --git a/api/src/main/java/org/asynchttpclient/cookie/KeyValuePairsParser.java b/api/src/main/java/org/asynchttpclient/cookie/KeyValuePairsParser.java deleted file mode 100644 index 795de30a52..0000000000 --- a/api/src/main/java/org/asynchttpclient/cookie/KeyValuePairsParser.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.cookie; - -import org.asynchttpclient.date.RFC2616Date; -import org.asynchttpclient.date.RFC2616DateParser; -import org.asynchttpclient.date.TimeConverter; - -/** - * A companion for CookieDecoder that parses key-value pairs (cookie name/value - * and attributes). - * - * @author slandelle - */ -class KeyValuePairsParser { - - private final TimeConverter timeBuilder; - private String name; - private String value; - private String rawValue; - private String domain; - private String path; - private long expires = -1L; - private int maxAge = -1; - private boolean secure; - private boolean httpOnly; - - /** - * @param timeBuilder used for parsing expires attribute - */ - public KeyValuePairsParser(TimeConverter timeBuilder) { - this.timeBuilder = timeBuilder; - } - - public Cookie cookie() { - return name != null ? new Cookie(name, value, rawValue, domain, path, expires, maxAge, secure, httpOnly) : null; - } - - /** - * Parse and store a key-value pair. First one is considered to be the - * cookie name/value. Unknown attribute names are silently discarded. - * - * @param header the HTTP header - * @param keyStart where the key starts in the header - * @param keyEnd where the key ends in the header - * @param value the decoded value - * @param rawValue the raw value (only non null for cookie value) - */ - public void parseKeyValuePair(String header, int keyStart, int keyEnd, String value, String rawValue) { - - if (name == null) - setCookieNameValue(header, keyStart, keyEnd, value, rawValue); - else - setCookieAttribute(header, keyStart, keyEnd, value); - } - - private void setCookieNameValue(String header, int keyStart, int keyEnd, String value, String rawValue) { - name = header.substring(keyStart, keyEnd); - this.value = value; - this.rawValue = rawValue; - } - - private void setCookieAttribute(String header, int keyStart, int keyEnd, String value) { - - int length = keyEnd - keyStart; - - if (length == 4) - parse4(header, keyStart, value); - else if (length == 6) - parse6(header, keyStart, value); - else if (length == 7) - parse7(header, keyStart, value); - else if (length == 8) - parse8(header, keyStart, value); - } - - private boolean isPath(char c0, char c1, char c2, char c3) { - return (c0 == 'P' || c0 == 'p') && // - (c1 == 'a' || c1 == 'A') && // - (c2 == 't' || c2 == 'T') && // - (c3 == 'h' || c3 == 'H'); - } - - private void parse4(String header, int nameStart, String value) { - - char c0 = header.charAt(nameStart); - char c1 = header.charAt(nameStart + 1); - char c2 = header.charAt(nameStart + 2); - char c3 = header.charAt(nameStart + 3); - - if (isPath(c0, c1, c2, c3)) - path = value; - } - - private boolean isDomain(char c0, char c1, char c2, char c3, char c4, char c5) { - return (c0 == 'D' || c0 == 'd') && // - (c1 == 'o' || c1 == 'O') && // - (c2 == 'm' || c2 == 'M') && // - (c3 == 'a' || c3 == 'A') && // - (c4 == 'i' || c4 == 'I') && // - (c5 == 'n' || c5 == 'N'); - } - - private boolean isSecure(char c0, char c1, char c2, char c3, char c4, char c5) { - return (c0 == 'S' || c0 == 's') && // - (c1 == 'e' || c1 == 'E') && // - (c2 == 'c' || c2 == 'C') && // - (c3 == 'u' || c3 == 'U') && // - (c4 == 'r' || c4 == 'R') && // - (c5 == 'e' || c5 == 'E'); - } - - private void parse6(String header, int nameStart, String value) { - - char c0 = header.charAt(nameStart); - char c1 = header.charAt(nameStart + 1); - char c2 = header.charAt(nameStart + 2); - char c3 = header.charAt(nameStart + 3); - char c4 = header.charAt(nameStart + 4); - char c5 = header.charAt(nameStart + 5); - - if (isDomain(c0, c1, c2, c3, c4, c5)) - domain = value; - else if (isSecure(c0, c1, c2, c3, c4, c5)) - secure = true; - } - - private boolean isExpires(char c0, char c1, char c2, char c3, char c4, char c5, char c6) { - return (c0 == 'E' || c0 == 'e') && // - (c1 == 'x' || c1 == 'X') && // - (c2 == 'p' || c2 == 'P') && // - (c3 == 'i' || c3 == 'I') && // - (c4 == 'r' || c4 == 'R') && // - (c5 == 'e' || c5 == 'E') && // - (c6 == 's' || c6 == 'S'); - } - - private boolean isMaxAge(char c0, char c1, char c2, char c3, char c4, char c5, char c6) { - return (c0 == 'M' || c0 == 'm') && // - (c1 == 'a' || c1 == 'A') && // - (c2 == 'x' || c2 == 'X') && // - (c3 == '-') && // - (c4 == 'A' || c4 == 'a') && // - (c5 == 'g' || c5 == 'G') && // - (c6 == 'e' || c6 == 'E'); - } - - private void setExpire(String value) { - - RFC2616Date dateElements = new RFC2616DateParser(value).parse(); - if (dateElements != null) { - try { - expires = timeBuilder.toTime(dateElements); - } catch (Exception e1) { - // ignore failure to parse -> treat as session cookie - } - } - } - - private void setMaxAge(String value) { - try { - maxAge = Math.max(Integer.valueOf(value), 0); - } catch (NumberFormatException e1) { - // ignore failure to parse -> treat as session cookie - } - } - - private void parse7(String header, int nameStart, String value) { - - char c0 = header.charAt(nameStart); - char c1 = header.charAt(nameStart + 1); - char c2 = header.charAt(nameStart + 2); - char c3 = header.charAt(nameStart + 3); - char c4 = header.charAt(nameStart + 4); - char c5 = header.charAt(nameStart + 5); - char c6 = header.charAt(nameStart + 6); - - if (isExpires(c0, c1, c2, c3, c4, c5, c6)) - setExpire(value); - - else if (isMaxAge(c0, c1, c2, c3, c4, c5, c6)) - setMaxAge(value); - } - - private boolean isHttpOnly(char c0, char c1, char c2, char c3, char c4, char c5, char c6, char c7) { - return (c0 == 'H' || c0 == 'h') && // - (c1 == 't' || c1 == 'T') && // - (c2 == 't' || c2 == 'T') && // - (c3 == 'p' || c3 == 'P') && // - (c4 == 'O' || c4 == 'o') && // - (c5 == 'n' || c5 == 'N') && // - (c6 == 'l' || c6 == 'L') && // - (c7 == 'y' || c7 == 'Y'); - } - - private void parse8(String header, int nameStart, String value) { - - char c0 = header.charAt(nameStart); - char c1 = header.charAt(nameStart + 1); - char c2 = header.charAt(nameStart + 2); - char c3 = header.charAt(nameStart + 3); - char c4 = header.charAt(nameStart + 4); - char c5 = header.charAt(nameStart + 5); - char c6 = header.charAt(nameStart + 6); - char c7 = header.charAt(nameStart + 7); - - if (isHttpOnly(c0, c1, c2, c3, c4, c5, c6, c7)) - httpOnly = true; - } -} diff --git a/api/src/main/java/org/asynchttpclient/date/CalendarTimeConverter.java b/api/src/main/java/org/asynchttpclient/date/CalendarTimeConverter.java deleted file mode 100644 index edc3061db2..0000000000 --- a/api/src/main/java/org/asynchttpclient/date/CalendarTimeConverter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.date; - -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.TimeZone; - -/** - * Calendar based TimeConverter. - * Note that a Joda-Time or DateTime based implementation would be more efficient, but AHC doesn't have a dependency to JodaTime. - * - * @author slandelle - */ -public class CalendarTimeConverter implements TimeConverter { - - public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); - - @Override - public long toTime(RFC2616Date dateElements) { - - Calendar calendar = new GregorianCalendar(// - dateElements.year(), // - dateElements.month() - 1, // beware, Calendar use months from 0 to 11 - dateElements.dayOfMonth(), // - dateElements.hour(), // - dateElements.minute(), // - dateElements.second()); - calendar.setTimeZone(GMT); - return calendar.getTimeInMillis(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/date/RFC2616Date.java b/api/src/main/java/org/asynchttpclient/date/RFC2616Date.java deleted file mode 100644 index 247870bd5a..0000000000 --- a/api/src/main/java/org/asynchttpclient/date/RFC2616Date.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.date; - -/** - * A placeholder for RFC2616 date elements - * - * @author slandelle - */ -public final class RFC2616Date { - - private final int year; - // 1 to 12 - private final int month; - private final int dayOfMonth; - private final int hour; - private final int minute; - private final int second; - - public RFC2616Date(int year, int month, int dayOfMonth, int hour, int minute, int second) { - this.year = year; - this.month = month; - this.dayOfMonth = dayOfMonth; - this.hour = hour; - this.minute = minute; - this.second = second; - } - - public int year() { - return year; - } - - public int month() { - return month; - } - - public int dayOfMonth() { - return dayOfMonth; - } - - public int hour() { - return hour; - } - - public int minute() { - return minute; - } - - public int second() { - return second; - } - - public static final class Builder { - - private int dayOfMonth; - private int month; - private int year; - private int hour; - private int minute; - private int second; - - public void setDayOfMonth(int dayOfMonth) { - this.dayOfMonth = dayOfMonth; - } - - public void setJanuary() { - month = 1; - } - - public void setFebruary() { - month = 2; - } - - public void setMarch() { - month = 3; - } - - public void setApril() { - month = 4; - } - - public void setMay() { - month = 5; - } - - public void setJune() { - month = 6; - } - - public void setJuly() { - month = 7; - } - - public void setAugust() { - month = 8; - } - - public void setSeptember() { - month = 9; - } - - public void setOctobre() { - month = 10; - } - - public void setNovembre() { - month = 11; - } - - public void setDecember() { - month = 12; - } - - public void setYear(int year) { - this.year = year; - } - - public void setHour(int hour) { - this.hour = hour; - } - - public void setMinute(int minute) { - this.minute = minute; - } - - public void setSecond(int second) { - this.second = second; - } - - public RFC2616Date build() { - return new RFC2616Date(year, month, dayOfMonth, hour, minute, second); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/date/RFC2616DateParser.java b/api/src/main/java/org/asynchttpclient/date/RFC2616DateParser.java deleted file mode 100644 index effc5168a9..0000000000 --- a/api/src/main/java/org/asynchttpclient/date/RFC2616DateParser.java +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.date; - -import org.asynchttpclient.date.RFC2616Date.Builder; - -/** - * A parser for RFC2616 - * Date format. - * - * @author slandelle - */ -public class RFC2616DateParser { - - private final String string; - private final int offset; - private final int length; - - /** - * @param string a string that will be fully parsed - */ - public RFC2616DateParser(String string) { - this(string, 0, string.length()); - } - - /** - * @param string the string to be parsed - * @param offset the offset where to start parsing - * @param length the number of chars to parse - */ - public RFC2616DateParser(String string, int offset, int length) { - - if (string.length() + offset < length) - throw new IllegalArgumentException("String length doesn't match offset and length"); - - this.string = string; - this.offset = offset; - this.length = length; - } - - private static class Tokens { - public final int[] starts; - public final int[] ends; - public final int length; - - public Tokens(int[] starts, int[] ends, int length) { - this.starts = starts; - this.ends = ends; - this.length = length; - } - } - - private Tokens tokenize() { - - int[] starts = new int[8]; - int[] ends = new int[8]; - boolean inToken = false; - int tokenCount = 0; - - int end = offset + length; - for (int i = offset; i < end; i++) { - - char c = string.charAt(i); - if (c == ' ' || c == ',' || c == '-' || c == ':') { - if (inToken) { - ends[tokenCount++] = i; - inToken = false; - } - } else if (!inToken) { - starts[tokenCount] = i; - inToken = true; - } - } - - // finish lastToken - if (inToken == true) - ends[tokenCount++] = end; - - return new Tokens(starts, ends, tokenCount); - } - - /** - * @param validate if validation is to be enabled of non-critical elements, - * such as day of week and timezone - * @return null is the string is not a valid RFC2616 date - */ - public RFC2616Date parse() { - - Tokens tokens = tokenize(); - - if (tokens.length != 7 && tokens.length != 8) - return null; - - // 1st token is ignored: ignore day of week - // 8th token is ignored: supposed to always be GMT - - if (isDigit(string.charAt(tokens.starts[1]))) - return buildDate(tokens); - else - return buildANSICDate(tokens); - } - - private RFC2616Date buildDate(Tokens tokens) { - - // Sun, 06 Nov 1994 08:49:37 GMT - - Builder dateBuilder = new Builder(); - - if (isValidDayOfMonth(tokens.starts[1], tokens.ends[1], dateBuilder) && // - isValidMonth(tokens.starts[2], tokens.ends[2], dateBuilder) && // - isValidYear(tokens.starts[3], tokens.ends[3], dateBuilder) && // - isValidHour(tokens.starts[4], tokens.ends[4], dateBuilder) && // - isValidMinuteSecond(tokens.starts[5], tokens.ends[5], dateBuilder, true) && // - isValidMinuteSecond(tokens.starts[6], tokens.ends[6], dateBuilder, false)) { - return dateBuilder.build(); - } - - return null; - } - - private RFC2616Date buildANSICDate(Tokens tokens) { - - // Sun Nov 6 08:49:37 1994 - - Builder dateBuilder = new Builder(); - - if (isValidMonth(tokens.starts[1], tokens.ends[1], dateBuilder) && // - isValidDayOfMonth(tokens.starts[2], tokens.ends[2], dateBuilder) && // - isValidHour(tokens.starts[3], tokens.ends[3], dateBuilder) && // - isValidMinuteSecond(tokens.starts[4], tokens.ends[4], dateBuilder, true) && // - isValidMinuteSecond(tokens.starts[5], tokens.ends[5], dateBuilder, false) && // - isValidYear(tokens.starts[6], tokens.ends[6], dateBuilder)) { - return dateBuilder.build(); - } - - return null; - } - - private boolean isValid1DigitDayOfMonth(char c0, Builder dateBuilder) { - if (isDigit(c0)) { - dateBuilder.setDayOfMonth(getNumericValue(c0)); - return true; - } - return false; - } - - private boolean isValid2DigitsDayOfMonth(char c0, char c1, Builder dateBuilder) { - if (isDigit(c0) && isDigit(c1)) { - int i0 = getNumericValue(c0); - int i1 = getNumericValue(c1); - int day = i0 * 10 + i1; - if (day <= 31) { - dateBuilder.setDayOfMonth(day); - return true; - } - } - return false; - } - - private boolean isValidDayOfMonth(int start, int end, Builder dateBuilder) { - - int tokenLength = end - start; - - if (tokenLength == 1) { - char c0 = string.charAt(start); - return isValid1DigitDayOfMonth(c0, dateBuilder); - - } else if (tokenLength == 2) { - char c0 = string.charAt(start); - char c1 = string.charAt(start + 1); - return isValid2DigitsDayOfMonth(c0, c1, dateBuilder); - } - return false; - } - - private boolean isValidJanuaryJuneJuly(char c0, char c1, char c2, Builder dateBuilder) { - if (c0 == 'J' || c0 == 'j') - if (c1 == 'a' || c1 == 'A') { - if (c2 == 'n' || c2 == 'N') { - dateBuilder.setJanuary(); - return true; - } - } else if (c1 == 'u' || c1 == 'U') { - if (c2 == 'n' || c2 == 'N') { - dateBuilder.setJune(); - return true; - } else if (c2 == 'l' || c2 == 'L') { - dateBuilder.setJuly(); - return true; - } - } - return false; - } - - private boolean isValidFebruary(char c0, char c1, char c2, Builder dateBuilder) { - if ((c0 == 'F' || c0 == 'f') && (c1 == 'e' || c1 == 'E') && (c2 == 'b' || c2 == 'B')) { - dateBuilder.setFebruary(); - return true; - } - return false; - } - - private boolean isValidMarchMay(char c0, char c1, char c2, Builder dateBuilder) { - if ((c0 == 'M' || c0 == 'm') && (c1 == 'a' || c1 == 'A')) { - if (c2 == 'r' || c2 == 'R') { - dateBuilder.setMarch(); - return true; - } else if (c2 == 'y' || c2 == 'M') { - dateBuilder.setMay(); - return true; - } - } - return false; - } - - private boolean isValidAprilAugust(char c0, char c1, char c2, Builder dateBuilder) { - if (c0 == 'A' || c0 == 'a') - if ((c1 == 'p' || c1 == 'P') && (c2 == 'r' || c2 == 'R')) { - dateBuilder.setApril(); - return true; - } else if ((c1 == 'u' || c1 == 'U') && (c2 == 'g' || c2 == 'G')) { - dateBuilder.setAugust(); - return true; - } - return false; - } - - private boolean isValidSeptember(char c0, char c1, char c2, Builder dateBuilder) { - if ((c0 == 'S' || c0 == 's') && (c1 == 'e' || c1 == 'E') && (c2 == 'p' || c2 == 'P')) { - dateBuilder.setSeptember(); - return true; - } - return false; - } - - private boolean isValidOctober(char c0, char c1, char c2, Builder dateBuilder) { - if ((c0 == 'O' || c0 == 'o') && (c1 == 'c' || c1 == 'C') && (c2 == 't' || c2 == 'T')) { - dateBuilder.setOctobre(); - return true; - } - return false; - } - - private boolean isValidNovember(char c0, char c1, char c2, Builder dateBuilder) { - if ((c0 == 'N' || c0 == 'n') && (c1 == 'o' || c1 == 'O') && (c2 == 'v' || c2 == 'V')) { - dateBuilder.setNovembre(); - return true; - } - return false; - } - - private boolean isValidDecember(char c0, char c1, char c2, Builder dateBuilder) { - if (c0 == 'D' || c0 == 'd') - if (c1 == 'e' || c1 == 'E') { - if (c2 == 'c' || c2 == 'C') { - dateBuilder.setDecember(); - return true; - } - } - return false; - } - - private boolean isValidMonth(int start, int end, Builder dateBuilder) { - - if (end - start != 3) - return false; - - char c0 = string.charAt(start); - char c1 = string.charAt(start + 1); - char c2 = string.charAt(start + 2); - - return isValidJanuaryJuneJuly(c0, c1, c2, dateBuilder) || // - isValidFebruary(c0, c1, c2, dateBuilder) || // - isValidMarchMay(c0, c1, c2, dateBuilder) || // - isValidAprilAugust(c0, c1, c2, dateBuilder) || // - isValidSeptember(c0, c1, c2, dateBuilder) || // - isValidOctober(c0, c1, c2, dateBuilder) || // - isValidNovember(c0, c1, c2, dateBuilder) || // - isValidDecember(c0, c1, c2, dateBuilder); - } - - private boolean isValid2DigitsYear(char c0, char c1, Builder dateBuilder) { - if (isDigit(c0) && isDigit(c1)) { - int i0 = getNumericValue(c0); - int i1 = getNumericValue(c1); - int year = i0 * 10 + i1; - year = year < 70 ? year + 2000 : year + 1900; - - return setValidYear(year, dateBuilder); - } - return false; - } - - private boolean isValid4DigitsYear(char c0, char c1, char c2, char c3, Builder dateBuilder) { - if (isDigit(c0) && isDigit(c1) && isDigit(c2) && isDigit(c3)) { - int i0 = getNumericValue(c0); - int i1 = getNumericValue(c1); - int i2 = getNumericValue(c2); - int i3 = getNumericValue(c3); - int year = i0 * 1000 + i1 * 100 + i2 * 10 + i3; - - return setValidYear(year, dateBuilder); - } - return false; - } - - private boolean setValidYear(int year, Builder dateBuilder) { - if (year >= 1601) { - dateBuilder.setYear(year); - return true; - } - return false; - } - - private boolean isValidYear(int start, int end, Builder dateBuilder) { - - int length = end - start; - - if (length == 2) { - char c0 = string.charAt(start); - char c1 = string.charAt(start + 1); - return isValid2DigitsYear(c0, c1, dateBuilder); - - } else if (length == 4) { - char c0 = string.charAt(start); - char c1 = string.charAt(start + 1); - char c2 = string.charAt(start + 2); - char c3 = string.charAt(start + 3); - return isValid4DigitsYear(c0, c1, c2, c3, dateBuilder); - } - - return false; - } - - private boolean isValid1DigitHour(char c0, Builder dateBuilder) { - if (isDigit(c0)) { - int hour = getNumericValue(c0); - dateBuilder.setHour(hour); - return true; - } - return false; - } - - private boolean isValid2DigitsHour(char c0, char c1, Builder dateBuilder) { - if (isDigit(c0) && isDigit(c1)) { - int i0 = getNumericValue(c0); - int i1 = getNumericValue(c1); - int hour = i0 * 10 + i1; - if (hour <= 24) { - dateBuilder.setHour(hour); - return true; - } - } - return false; - } - - private boolean isValidHour(int start, int end, Builder dateBuilder) { - - int length = end - start; - - if (length == 1) { - char c0 = string.charAt(start); - return isValid1DigitHour(c0, dateBuilder); - - } else if (length == 2) { - char c0 = string.charAt(start); - char c1 = string.charAt(start + 1); - return isValid2DigitsHour(c0, c1, dateBuilder); - } - return false; - } - - private boolean isValid1DigitMinuteSecond(char c0, Builder dateBuilder, boolean minuteOrSecond) { - if (isDigit(c0)) { - int value = getNumericValue(c0); - if (minuteOrSecond) - dateBuilder.setMinute(value); - else - dateBuilder.setSecond(value); - return true; - } - return false; - } - - private boolean isValid2DigitsMinuteSecond(char c0, char c1, Builder dateBuilder, boolean minuteOrSecond) { - if (isDigit(c0) && isDigit(c1)) { - int i0 = getNumericValue(c0); - int i1 = getNumericValue(c1); - int value = i0 * 10 + i1; - if (value <= 60) { - if (minuteOrSecond) - dateBuilder.setMinute(value); - else - dateBuilder.setSecond(value); - return true; - } - } - return false; - } - - private boolean isValidMinuteSecond(int start, int end, Builder dateBuilder, boolean minuteOrSecond) { - - int length = end - start; - - if (length == 1) { - char c0 = string.charAt(start); - return isValid1DigitMinuteSecond(c0, dateBuilder, minuteOrSecond); - - } else if (length == 2) { - char c0 = string.charAt(start); - char c1 = string.charAt(start + 1); - return isValid2DigitsMinuteSecond(c0, c1, dateBuilder, minuteOrSecond); - } - return false; - } - - private boolean isDigit(char c) { - return c >= '0' && c <= '9'; - } - - private int getNumericValue(char c) { - return (int) c - 48; - } -} diff --git a/api/src/main/java/org/asynchttpclient/date/TimeConverter.java b/api/src/main/java/org/asynchttpclient/date/TimeConverter.java deleted file mode 100644 index 5b7cda67c7..0000000000 --- a/api/src/main/java/org/asynchttpclient/date/TimeConverter.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.date; - -/** - * Converts a RFC2616Date to time in millis - * - * @author slandelle - */ -public interface TimeConverter { - - long toTime(RFC2616Date dateElements); -} diff --git a/api/src/main/java/org/asynchttpclient/extra/AsyncHandlerWrapper.java b/api/src/main/java/org/asynchttpclient/extra/AsyncHandlerWrapper.java deleted file mode 100644 index 3ce9527270..0000000000 --- a/api/src/main/java/org/asynchttpclient/extra/AsyncHandlerWrapper.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.asynchttpclient.extra; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; - -public class AsyncHandlerWrapper implements AsyncHandler { - - private static final Logger LOGGER = LoggerFactory.getLogger(AsyncHandlerWrapper.class); - private final AsyncHandler asyncHandler; - private final Semaphore available; - private final AtomicBoolean complete = new AtomicBoolean(false); - - public AsyncHandlerWrapper(AsyncHandler asyncHandler, Semaphore available) { - this.asyncHandler = asyncHandler; - this.available = available; - } - - private void complete() { - if (complete.compareAndSet(false, true)) - available.release(); - if (LOGGER.isDebugEnabled()) - LOGGER.debug("Current Throttling Status after onThrowable {}", available.availablePermits()); - } - - /** - * {@inheritDoc} - */ - @Override - public void onThrowable(Throwable t) { - try { - asyncHandler.onThrowable(t); - } finally { - complete(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - return asyncHandler.onBodyPartReceived(bodyPart); - } - - /** - * {@inheritDoc} - */ - @Override - public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { - return asyncHandler.onStatusReceived(responseStatus); - } - - /** - * {@inheritDoc} - */ - @Override - public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { - return asyncHandler.onHeadersReceived(headers); - } - - /** - * {@inheritDoc} - */ - @Override - public T onCompleted() throws Exception { - try { - return asyncHandler.onCompleted(); - } finally { - complete(); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/extra/ThrottleRequestFilter.java b/api/src/main/java/org/asynchttpclient/extra/ThrottleRequestFilter.java deleted file mode 100644 index 7dd44d34a1..0000000000 --- a/api/src/main/java/org/asynchttpclient/extra/ThrottleRequestFilter.java +++ /dev/null @@ -1,67 +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.extra; - -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.RequestFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -/** - * A {@link org.asynchttpclient.filter.RequestFilter} throttles requests and block when the number of permits is reached, waiting for - * the response to arrives before executing the next request. - */ -public class ThrottleRequestFilter implements RequestFilter { - private final static Logger logger = LoggerFactory.getLogger(ThrottleRequestFilter.class); - private final Semaphore available; - private final int maxWait; - - public ThrottleRequestFilter(int maxConnections) { - this(maxConnections, Integer.MAX_VALUE); - } - - public ThrottleRequestFilter(int maxConnections, int maxWait) { - this(maxConnections, maxWait, false); - } - - public ThrottleRequestFilter(int maxConnections, int maxWait, boolean fair) { - this.maxWait = maxWait; - available = new Semaphore(maxConnections, fair); - } - - /** - * {@inheritDoc} - */ - @Override - public FilterContext filter(FilterContext ctx) throws FilterException { - - try { - if (logger.isDebugEnabled()) { - logger.debug("Current Throttling Status {}", available.availablePermits()); - } - if (!available.tryAcquire(maxWait, TimeUnit.MILLISECONDS)) { - throw new FilterException(String.format("No slot available for processing Request %s with AsyncHandler %s", - ctx.getRequest(), ctx.getAsyncHandler())); - } - } catch (InterruptedException e) { - throw new FilterException(String.format("Interrupted Request %s with AsyncHandler %s", ctx.getRequest(), ctx.getAsyncHandler())); - } - - return new FilterContext.FilterContextBuilder(ctx).asyncHandler(new AsyncHandlerWrapper(ctx.getAsyncHandler(), available)) - .build(); - } -} \ No newline at end of file diff --git a/api/src/main/java/org/asynchttpclient/filter/FilterContext.java b/api/src/main/java/org/asynchttpclient/filter/FilterContext.java deleted file mode 100644 index dfc6f9de22..0000000000 --- a/api/src/main/java/org/asynchttpclient/filter/FilterContext.java +++ /dev/null @@ -1,161 +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; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Request; - -import java.io.IOException; - -/** - * A {@link FilterContext} can be used to decorate {@link Request} and {@link AsyncHandler} from a list of {@link RequestFilter}. - * {@link RequestFilter} gets executed before the HTTP request is made to the remote server. Once the response bytes are - * received, a {@link FilterContext} is then passed to the list of {@link ResponseFilter}. {@link ResponseFilter} - * gets invoked before the response gets processed, e.g. before authorization, redirection and invocation of {@link AsyncHandler} - * gets processed. - *

- * Invoking {@link FilterContext#getResponseStatus()} returns an instance of {@link HttpResponseStatus} - * that can be used to decide if the response processing should continue or not. You can stop the current response processing - * and replay the request but creating a {@link FilterContext}. The {@link org.asynchttpclient.AsyncHttpProvider} - * will interrupt the processing and "replay" the associated {@link Request} instance. - */ -public class FilterContext { - - private final FilterContextBuilder b; - - /** - * Create a new {@link FilterContext} - * - * @param b a {@link FilterContextBuilder} - */ - private FilterContext(FilterContextBuilder b) { - this.b = b; - } - - /** - * Return the original or decorated {@link AsyncHandler} - * - * @return the original or decorated {@link AsyncHandler} - */ - public AsyncHandler getAsyncHandler() { - return b.asyncHandler; - } - - /** - * Return the original or decorated {@link Request} - * - * @return the original or decorated {@link Request} - */ - public Request getRequest() { - return b.request; - } - - /** - * Return the unprocessed response's {@link HttpResponseStatus} - * - * @return the unprocessed response's {@link HttpResponseStatus} - */ - public HttpResponseStatus getResponseStatus() { - return b.responseStatus; - } - - /** - * Return the response {@link HttpResponseHeaders} - */ - public HttpResponseHeaders getResponseHeaders() { - return b.headers; - } - - /** - * Return true if the current response's processing needs to be interrupted and a new {@link Request} be executed. - * - * @return true if the current response's processing needs to be interrupted and a new {@link Request} be executed. - */ - public boolean replayRequest() { - return b.replayRequest; - } - - /** - * Return the {@link IOException} - * - * @return the {@link IOException} - */ - public IOException getIOException() { - return b.ioException; - } - - public static class FilterContextBuilder { - private AsyncHandler asyncHandler = null; - private Request request = null; - private HttpResponseStatus responseStatus = null; - private boolean replayRequest = false; - private IOException ioException = null; - private HttpResponseHeaders headers; - - public FilterContextBuilder() { - } - - public FilterContextBuilder(FilterContext clone) { - asyncHandler = clone.getAsyncHandler(); - request = clone.getRequest(); - responseStatus = clone.getResponseStatus(); - replayRequest = clone.replayRequest(); - ioException = clone.getIOException(); - } - - public AsyncHandler getAsyncHandler() { - return asyncHandler; - } - - public FilterContextBuilder asyncHandler(AsyncHandler asyncHandler) { - this.asyncHandler = asyncHandler; - return this; - } - - public Request getRequest() { - return request; - } - - public FilterContextBuilder request(Request request) { - this.request = request; - return this; - } - - public FilterContextBuilder responseStatus(HttpResponseStatus responseStatus) { - this.responseStatus = responseStatus; - return this; - } - - public FilterContextBuilder responseHeaders(HttpResponseHeaders headers) { - this.headers = headers; - return this; - } - - public FilterContextBuilder replayRequest(boolean replayRequest) { - this.replayRequest = replayRequest; - return this; - } - - public FilterContextBuilder ioException(IOException ioException) { - this.ioException = ioException; - return this; - } - - public FilterContext build() { - return new FilterContext(this); - } - } - -} diff --git a/api/src/main/java/org/asynchttpclient/filter/FilterException.java b/api/src/main/java/org/asynchttpclient/filter/FilterException.java deleted file mode 100644 index 739ecf7748..0000000000 --- a/api/src/main/java/org/asynchttpclient/filter/FilterException.java +++ /dev/null @@ -1,36 +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; - -/** - * An exception that can be thrown by an {@link org.asynchttpclient.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 { - - /** - * @param message - */ - public FilterException(final String message) { - super(message); - } - - /** - * @param message - * @param cause - */ - public FilterException(final String message, final Throwable cause) { - super(message, cause); - } -} diff --git a/api/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java b/api/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java deleted file mode 100644 index 99b3ee2ee0..0000000000 --- a/api/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java +++ /dev/null @@ -1,29 +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; - -/** - * This filter is invoked when an {@link java.io.IOException} occurs during an http transaction. - */ -public interface IOExceptionFilter { - - /** - * An {@link org.asynchttpclient.AsyncHttpProvider} will invoke {@link IOExceptionFilter#filter} and will - * use the returned {@link FilterContext} to replay the {@link org.asynchttpclient.Request} or abort the processing. - * - * @param ctx a {@link FilterContext} - * @return {@link FilterContext}. The {@link FilterContext} instance may not the same as the original one. - * @throws FilterException to interrupt the filter processing. - */ - FilterContext filter(FilterContext ctx) throws FilterException; -} diff --git a/api/src/main/java/org/asynchttpclient/generators/ByteArrayBodyGenerator.java b/api/src/main/java/org/asynchttpclient/generators/ByteArrayBodyGenerator.java deleted file mode 100644 index f1e0a97752..0000000000 --- a/api/src/main/java/org/asynchttpclient/generators/ByteArrayBodyGenerator.java +++ /dev/null @@ -1,71 +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.generators; - -import org.asynchttpclient.Body; -import org.asynchttpclient.BodyGenerator; - -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * A {@link BodyGenerator} backed by a byte array. - */ -public class ByteArrayBodyGenerator implements BodyGenerator { - - private final byte[] bytes; - - public ByteArrayBodyGenerator(byte[] bytes) { - this.bytes = bytes; - } - - protected final class ByteBody implements Body { - private boolean eof = false; - private int lastPosition = 0; - - public long getContentLength() { - return bytes.length; - } - - public long read(ByteBuffer byteBuffer) throws IOException { - - if (eof) { - return -1; - } - - final int remaining = bytes.length - lastPosition; - if (remaining <= byteBuffer.capacity()) { - byteBuffer.put(bytes, lastPosition, remaining); - eof = true; - return remaining; - } else { - byteBuffer.put(bytes, lastPosition, byteBuffer.capacity()); - lastPosition = lastPosition + byteBuffer.capacity(); - return byteBuffer.capacity(); - } - } - - public void close() throws IOException { - lastPosition = 0; - eof = false; - } - } - - /** - * {@inheritDoc} - */ - @Override - public Body createBody() throws IOException { - return new ByteBody(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/generators/FileBodyGenerator.java b/api/src/main/java/org/asynchttpclient/generators/FileBodyGenerator.java deleted file mode 100644 index dd97ec217b..0000000000 --- a/api/src/main/java/org/asynchttpclient/generators/FileBodyGenerator.java +++ /dev/null @@ -1,101 +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.generators; - -import org.asynchttpclient.BodyGenerator; -import org.asynchttpclient.RandomAccessBody; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.WritableByteChannel; - -/** - * Creates a request body from the contents of a file. - */ -//Not used by Netty -public class FileBodyGenerator implements BodyGenerator { - - private final File file; - private final long regionSeek; - private final long regionLength; - - public FileBodyGenerator(File file) { - this(file, 0L, file.length()); - } - - public FileBodyGenerator(File file, long regionSeek, long regionLength) { - if (file == null) { - throw new NullPointerException("file"); - } - this.file = file; - this.regionLength = regionLength; - this.regionSeek = regionSeek; - } - - public File getFile() { - return file; - } - - public long getRegionLength() { - return regionLength; - } - - public long getRegionSeek() { - return regionSeek; - } - - /** - * {@inheritDoc} - */ - @Override - public RandomAccessBody createBody() throws IOException { - return new FileBody(file, regionSeek, regionLength); - } - - private static class FileBody implements RandomAccessBody { - - private final RandomAccessFile raf; - - private final FileChannel channel; - - private final long length; - - private FileBody(File file, long regionSeek, long regionLength) throws IOException { - raf = new RandomAccessFile(file, "r"); - channel = raf.getChannel(); - length = regionLength; - if (regionSeek > 0) { - raf.seek(regionSeek); - } - } - - public long getContentLength() { - return length; - } - - public long read(ByteBuffer buffer) throws IOException { - return channel.read(buffer); - } - - public long transferTo(long position, WritableByteChannel target) throws IOException { - return channel.transferTo(position, length, target); - } - - public void close() throws IOException { - raf.close(); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/generators/InputStreamBodyGenerator.java b/api/src/main/java/org/asynchttpclient/generators/InputStreamBodyGenerator.java deleted file mode 100644 index 34be273b95..0000000000 --- a/api/src/main/java/org/asynchttpclient/generators/InputStreamBodyGenerator.java +++ /dev/null @@ -1,97 +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.generators; - -import org.asynchttpclient.Body; -import org.asynchttpclient.BodyGenerator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; - -/** - * 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 java.io.InputStream#reset()} operation. If not, mechanisms like authentication, redirect, or - * resumable download will not works. - */ -public class InputStreamBodyGenerator implements BodyGenerator { - - private final InputStream inputStream; - - public InputStreamBodyGenerator(InputStream inputStream) { - this.inputStream = inputStream; - } - - public InputStream getInputStream() { - return inputStream; - } - - /** - * {@inheritDoc} - */ - @Override - public Body createBody() throws IOException { - return new InputStreamBody(inputStream); - } - - private static class InputStreamBody implements Body { - - private static final Logger LOGGER = LoggerFactory.getLogger(InputStreamBody.class); - - private final InputStream inputStream; - private byte[] chunk; - - private InputStreamBody(InputStream inputStream) { - this.inputStream = inputStream; - if (inputStream.markSupported()) { - inputStream.mark(0); - } else { - LOGGER.info("inputStream.markSupported() not supported. Some features will not work."); - } - } - - public long getContentLength() { - return -1L; - } - - public long read(ByteBuffer buffer) throws IOException { - - // To be safe. - chunk = new byte[buffer.remaining() - 10]; - - int read = -1; - try { - read = inputStream.read(chunk); - } catch (IOException ex) { - LOGGER.warn("Unable to read", ex); - } - - if (read > 0) { - buffer.put(chunk, 0, read); - } else { - if (inputStream.markSupported()) { - inputStream.reset(); - } - } - return read; - } - - public void close() throws IOException { - inputStream.close(); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/listenable/AbstractListenableFuture.java b/api/src/main/java/org/asynchttpclient/listenable/AbstractListenableFuture.java deleted file mode 100644 index be0d81842f..0000000000 --- a/api/src/main/java/org/asynchttpclient/listenable/AbstractListenableFuture.java +++ /dev/null @@ -1,69 +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. - */ -/* - * Copyright (C) 2007 Google Inc. - * - * 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.listenable; - -import org.asynchttpclient.ListenableFuture; - -import java.util.concurrent.Executor; - -/** - *

An abstract base implementation of the listener support provided by - * {@link ListenableFuture}. This class uses an {@link ExecutionList} to - * guarantee that all registered listeners will be executed. Listener/Executor - * pairs are stored in the execution list and executed in the order in which - * they were added, but because of thread scheduling issues there is no - * guarantee that the JVM will execute them in order. In addition, listeners - * added after the task is complete will be executed immediately, even if some - * previously added listeners have not yet been executed. - * - * @author Sven Mawson - * @since 1 - */ -public abstract class AbstractListenableFuture implements ListenableFuture { - - // The execution list to hold our executors. - private final ExecutionList executionList = new ExecutionList(); - - /* - * Adds a listener/executor pair to execution list to execute when this task - * is completed. - */ - - public ListenableFuture addListener(Runnable listener, Executor exec) { - executionList.add(listener, exec); - return this; - } - - /* - * Execute the execution list. - */ - protected void runListeners() { - executionList.run(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/listenable/ExecutionList.java b/api/src/main/java/org/asynchttpclient/listenable/ExecutionList.java deleted file mode 100644 index 99347e792f..0000000000 --- a/api/src/main/java/org/asynchttpclient/listenable/ExecutionList.java +++ /dev/null @@ -1,136 +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. - */ -/* - * Copyright (C) 2007 Google Inc. - * - * 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.listenable; - -import java.util.Queue; -import java.util.concurrent.Executor; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - *

A list of ({@code Runnable}, {@code Executor}) pairs that guarantees - * that every {@code Runnable} that is added using the add method will be - * executed in its associated {@code Executor} after {@link #run()} is called. - * {@code Runnable}s added after {@code run} is called are still guaranteed to - * execute. - * - * @author Nishant Thakkar - * @author Sven Mawson - * @since 1 - */ -public final class ExecutionList implements Runnable { - - // Logger to log exceptions caught when running runnables. - private static final Logger log = Logger.getLogger(ExecutionList.class.getName()); - - // The runnable,executor pairs to execute. - private final Queue runnables = new LinkedBlockingQueue(); - - // Boolean we use mark when execution has started. Only accessed from within - // synchronized blocks. - private boolean executed = false; - - /** - * Add the runnable/executor pair to the list of pairs to execute. Executes - * the pair immediately if we've already started execution. - */ - public void add(Runnable runnable, Executor executor) { - - if (runnable == null) { - throw new NullPointerException("Runnable is null"); - } - - if (executor == null) { - throw new NullPointerException("Executor is null"); - } - - boolean executeImmediate = false; - - // Lock while we check state. We must maintain the lock while adding the - // new pair so that another thread can't run the list out from under us. - // We only add to the list if we have not yet started execution. - synchronized (runnables) { - if (!executed) { - runnables.add(new RunnableExecutorPair(runnable, executor)); - } else { - executeImmediate = true; - } - } - - // Execute the runnable immediately. Because of scheduling this may end up - // getting called before some of the previously added runnables, but we're - // ok with that. If we want to change the contract to guarantee ordering - // among runnables we'd have to modify the logic here to allow it. - if (executeImmediate) { - executor.execute(runnable); - } - } - - /** - * Runs this execution list, executing all pairs in the order they were - * added. Pairs added after this method has started executing the list will - * be executed immediately. - */ - public void run() { - - // Lock while we update our state so the add method above will finish adding - // any listeners before we start to run them. - synchronized (runnables) { - executed = true; - } - - // At this point the runnables will never be modified by another - // thread, so we are safe using it outside of the synchronized block. - while (!runnables.isEmpty()) { - runnables.poll().execute(); - } - } - - private static class RunnableExecutorPair { - final Runnable runnable; - final Executor executor; - - RunnableExecutorPair(Runnable runnable, Executor executor) { - this.runnable = runnable; - this.executor = executor; - } - - void execute() { - try { - executor.execute(runnable); - } catch (RuntimeException e) { - // Log it and keep going, bad runnable and/or executor. Don't - // punish the other runnables if we're given a bad one. We only - // catch RuntimeException because we want Errors to propagate up. - log.log(Level.SEVERE, "RuntimeException while executing runnable " + runnable + " with executor " + executor, e); - } - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/listener/TransferCompletionHandler.java b/api/src/main/java/org/asynchttpclient/listener/TransferCompletionHandler.java deleted file mode 100644 index b39930dffd..0000000000 --- a/api/src/main/java/org/asynchttpclient/listener/TransferCompletionHandler.java +++ /dev/null @@ -1,218 +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.listener; - -import org.asynchttpclient.AsyncCompletionHandlerBase; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.ConcurrentLinkedQueue; - -/** - * A {@link org.asynchttpclient.AsyncHandler} that can be used to notify a set of {@link TransferListener} - *

- *

- * - *
- * AsyncHttpClient client = new AsyncHttpClient();
- * TransferCompletionHandler tl = new TransferCompletionHandler();
- * tl.addTransferListener(new TransferListener() {
- * 

- * public void onRequestHeadersSent(FluentCaseInsensitiveStringsMap headers) { - * } - *

- * public void onResponseHeadersReceived(FluentCaseInsensitiveStringsMap headers) { - * } - *

- * public void onBytesReceived(ByteBuffer buffer) { - * } - *

- * public void onBytesSent(long amount, long current, long total) { - * } - *

- * public void onRequestResponseCompleted() { - * } - *

- * public void onThrowable(Throwable t) { - * } - * }); - *

- * Response response = httpClient.prepareGet("http://.../").execute(tl).get(); - *

- * - *
- */ -public class TransferCompletionHandler extends AsyncCompletionHandlerBase { - private final static Logger logger = LoggerFactory.getLogger(TransferCompletionHandler.class); - private final ConcurrentLinkedQueue listeners = new ConcurrentLinkedQueue(); - private final boolean accumulateResponseBytes; - private FluentCaseInsensitiveStringsMap headers; - - /** - * Create a TransferCompletionHandler that will not accumulate bytes. The resulting {@link org.asynchttpclient.Response#getResponseBody()}, - * {@link org.asynchttpclient.Response#getResponseBodyAsStream()} and {@link Response#getResponseBodyExcerpt(int)} will throw an IllegalStateException if called. - */ - public TransferCompletionHandler() { - this(false); - } - - /** - * Create a TransferCompletionHandler that can or cannot accumulate bytes and make it available when {@link org.asynchttpclient.Response#getResponseBody()} get called. The - * default is false. - * - * @param accumulateResponseBytes - * true to accumulates bytes in memory. - */ - public TransferCompletionHandler(boolean accumulateResponseBytes) { - this.accumulateResponseBytes = accumulateResponseBytes; - } - - /** - * Add a {@link TransferListener} - * - * @param t - * a {@link TransferListener} - * @return this - */ - public TransferCompletionHandler addTransferListener(TransferListener t) { - listeners.offer(t); - return this; - } - - /** - * Remove a {@link TransferListener} - * - * @param t - * a {@link TransferListener} - * @return this - */ - public TransferCompletionHandler removeTransferListener(TransferListener t) { - listeners.remove(t); - return this; - } - - /** - * Set headers to this listener. - * - * @param headers - * {@link FluentCaseInsensitiveStringsMap} - */ - public void headers(FluentCaseInsensitiveStringsMap headers) { - this.headers = headers; - } - - @Override - public STATE onHeadersReceived(final HttpResponseHeaders headers) throws Exception { - fireOnHeaderReceived(headers.getHeaders()); - return super.onHeadersReceived(headers); - } - - @Override - public STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { - STATE s = STATE.CONTINUE; - if (accumulateResponseBytes) { - s = super.onBodyPartReceived(content); - } - fireOnBytesReceived(content.getBodyPartBytes()); - return s; - } - - @Override - public Response onCompleted(Response response) throws Exception { - fireOnEnd(); - return response; - } - - @Override - public STATE onHeaderWriteCompleted() { - if (headers != null) { - fireOnHeadersSent(headers); - } - return STATE.CONTINUE; - } - - @Override - public STATE onContentWriteProgress(long amount, long current, long total) { - fireOnBytesSent(amount, current, total); - return STATE.CONTINUE; - } - - @Override - public void onThrowable(Throwable t) { - fireOnThrowable(t); - } - - private void fireOnHeadersSent(FluentCaseInsensitiveStringsMap headers) { - for (TransferListener l : listeners) { - try { - l.onRequestHeadersSent(headers); - } catch (Throwable t) { - l.onThrowable(t); - } - } - } - - private void fireOnHeaderReceived(FluentCaseInsensitiveStringsMap headers) { - for (TransferListener l : listeners) { - try { - l.onResponseHeadersReceived(headers); - } catch (Throwable t) { - l.onThrowable(t); - } - } - } - - private void fireOnEnd() { - for (TransferListener l : listeners) { - try { - l.onRequestResponseCompleted(); - } catch (Throwable t) { - l.onThrowable(t); - } - } - } - - private void fireOnBytesReceived(byte[] b) { - for (TransferListener l : listeners) { - try { - l.onBytesReceived(b); - } catch (Throwable t) { - l.onThrowable(t); - } - } - } - - private void fireOnBytesSent(long amount, long current, long total) { - for (TransferListener l : listeners) { - try { - l.onBytesSent(amount, current, total); - } catch (Throwable t) { - l.onThrowable(t); - } - } - } - - private void fireOnThrowable(Throwable t) { - for (TransferListener l : listeners) { - try { - l.onThrowable(t); - } catch (Throwable t2) { - logger.warn("onThrowable", t2); - } - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/multipart/AbstractFilePart.java b/api/src/main/java/org/asynchttpclient/multipart/AbstractFilePart.java deleted file mode 100644 index c3a2533603..0000000000 --- a/api/src/main/java/org/asynchttpclient/multipart/AbstractFilePart.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.multipart; - -import static org.asynchttpclient.util.StandardCharsets.US_ASCII; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.Charset; - -/** - * This class is an adaptation of the Apache HttpClient implementation - * - * @link http://hc.apache.org/httpclient-3.x/ - */ -public abstract class AbstractFilePart extends PartBase { - - /** - * Default content encoding of file attachments. - */ - public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream"; - - /** - * Default transfer encoding of file attachments. - */ - public static final String DEFAULT_TRANSFER_ENCODING = "binary"; - - /** - * Attachment's file name as a byte array - */ - private static final byte[] FILE_NAME_BYTES = "; filename=".getBytes(US_ASCII); - - private long stalledTime = -1L; - - private String fileName; - - /** - * FilePart Constructor. - * - * @param name - * the name for this part - * @param partSource - * the source for this part - * @param contentType - * the content type for this part, if null the {@link #DEFAULT_CONTENT_TYPE default} is used - * @param charset - * the charset encoding for this part - */ - public AbstractFilePart(String name, String contentType, Charset charset, String contentId, String transfertEncoding) { - super(name,// - contentType == null ? DEFAULT_CONTENT_TYPE : contentType,// - charset,// - contentId,// - transfertEncoding == null ? DEFAULT_TRANSFER_ENCODING : transfertEncoding); - } - - protected void visitDispositionHeader(PartVisitor visitor) throws IOException { - super.visitDispositionHeader(visitor); - if (fileName != null) { - visitor.withBytes(FILE_NAME_BYTES); - visitor.withByte(QUOTE_BYTE); - visitor.withBytes(fileName.getBytes(US_ASCII)); - visitor.withByte(QUOTE_BYTE); - } - } - - protected byte[] generateFileStart(byte[] boundary) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - OutputStreamPartVisitor visitor = new OutputStreamPartVisitor(out); - visitStart(visitor, boundary); - visitDispositionHeader(visitor); - visitContentTypeHeader(visitor); - visitTransferEncodingHeader(visitor); - visitContentIdHeader(visitor); - visitEndOfHeader(visitor); - - return out.toByteArray(); - } - - protected byte[] generateFileEnd() throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - OutputStreamPartVisitor visitor = new OutputStreamPartVisitor(out); - visitEnd(visitor); - return out.toByteArray(); - } - - public void setStalledTime(long ms) { - stalledTime = ms; - } - - public long getStalledTime() { - return stalledTime; - } - - public void setFileName(String fileName) { - this.fileName = fileName; - } - - public String getFileName() { - return fileName; - } - - @Override - public String toString() { - return new StringBuilder()// - .append(super.toString())// - .append(" filename=").append(fileName)// - .toString(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/multipart/ByteArrayPart.java b/api/src/main/java/org/asynchttpclient/multipart/ByteArrayPart.java deleted file mode 100644 index f3dc0f11c7..0000000000 --- a/api/src/main/java/org/asynchttpclient/multipart/ByteArrayPart.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.multipart; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.channels.WritableByteChannel; -import java.nio.charset.Charset; - -public class ByteArrayPart extends AbstractFilePart { - - private final byte[] bytes; - - public ByteArrayPart(String name, byte[] bytes) { - this(name, bytes, null); - } - - public ByteArrayPart(String name, byte[] bytes, String contentType) { - this(name, bytes, contentType, null); - } - - public ByteArrayPart(String name, byte[] bytes, String contentType, Charset charset) { - this(name, bytes, contentType, charset, null); - } - - public ByteArrayPart(String name, byte[] bytes, String contentType, Charset charset, String fileName) { - this(name, bytes, contentType, charset, fileName, null); - } - - public ByteArrayPart(String name, byte[] bytes, String contentType, Charset charset, String fileName, String contentId) { - this(name, bytes, contentType, charset, fileName, contentId, null); - } - - public ByteArrayPart(String name, byte[] bytes, String contentType, Charset charset, String fileName, String contentId, String transferEncoding) { - super(name, contentType, charset, contentId, transferEncoding); - if (bytes == null) - throw new NullPointerException("bytes"); - this.bytes = bytes; - setFileName(fileName); - } - - @Override - protected void sendData(OutputStream out) throws IOException { - out.write(bytes); - } - - @Override - protected long getDataLength() { - return bytes.length; - } - - public byte[] getBytes() { - return bytes; - } - - @Override - public long write(WritableByteChannel target, byte[] boundary) throws IOException { - FilePartStallHandler handler = new FilePartStallHandler(getStalledTime(), this); - - try { - handler.start(); - - long length = MultipartUtils.writeBytesToChannel(target, generateFileStart(boundary)); - length += MultipartUtils.writeBytesToChannel(target, bytes); - length += MultipartUtils.writeBytesToChannel(target, generateFileEnd()); - - return length; - } finally { - handler.completed(); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/multipart/CounterPartVisitor.java b/api/src/main/java/org/asynchttpclient/multipart/CounterPartVisitor.java deleted file mode 100644 index dc7b822163..0000000000 --- a/api/src/main/java/org/asynchttpclient/multipart/CounterPartVisitor.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.multipart; - -import java.io.IOException; - -public class CounterPartVisitor implements PartVisitor { - - private long count = 0L; - - @Override - public void withBytes(byte[] bytes) throws IOException { - count += bytes.length; - } - - @Override - public void withByte(byte b) throws IOException { - count++; - } - - public long getCount() { - return count; - } -} diff --git a/api/src/main/java/org/asynchttpclient/multipart/FilePart.java b/api/src/main/java/org/asynchttpclient/multipart/FilePart.java deleted file mode 100644 index 359ec21f72..0000000000 --- a/api/src/main/java/org/asynchttpclient/multipart/FilePart.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.multipart; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; -import java.nio.channels.WritableByteChannel; -import java.nio.charset.Charset; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class FilePart extends AbstractFilePart { - - private static final Logger LOGGER = LoggerFactory.getLogger(FilePart.class); - - private final File file; - - public FilePart(String name, File file) { - this(name, file, null); - } - - public FilePart(String name, File file, String contentType) { - this(name, file, contentType, null); - } - - public FilePart(String name, File file, String contentType, Charset charset) { - this(name, file, contentType, charset, null); - } - - public FilePart(String name, File file, String contentType, Charset charset, String fileName) { - this(name, file, contentType, charset, fileName, null); - } - - public FilePart(String name, File file, String contentType, Charset charset, String fileName, String contentId) { - this(name, file, contentType, charset, fileName, contentId, null); - } - - public FilePart(String name, File file, String contentType, Charset charset, String fileName, String contentId, String transferEncoding) { - super(name, contentType, charset, contentId, transferEncoding); - if (file == null) - throw new NullPointerException("file"); - if (!file.isFile()) - throw new IllegalArgumentException("File is not a normal file " + file.getAbsolutePath()); - if (!file.canRead()) - throw new IllegalArgumentException("File is not readable " + file.getAbsolutePath()); - this.file = file; - setFileName(fileName != null ? fileName : file.getName()); - } - - @Override - protected void sendData(OutputStream out) throws IOException { - if (getDataLength() == 0) { - - // this file contains no data, so there is nothing to send. - // we don't want to create a zero length buffer as this will - // cause an infinite loop when reading. - return; - } - - byte[] tmp = new byte[4096]; - InputStream instream = new FileInputStream(file); - try { - int len; - while ((len = instream.read(tmp)) >= 0) { - out.write(tmp, 0, len); - } - } finally { - // we're done with the stream, close it - instream.close(); - } - } - - @Override - protected long getDataLength() { - return file.length(); - } - - public File getFile() { - return file; - } - - @Override - public long write(WritableByteChannel target, byte[] boundary) throws IOException { - FilePartStallHandler handler = new FilePartStallHandler(getStalledTime(), this); - - handler.start(); - - int length = 0; - - length += MultipartUtils.writeBytesToChannel(target, generateFileStart(boundary)); - - RandomAccessFile raf = new RandomAccessFile(file, "r"); - FileChannel fc = raf.getChannel(); - - long l = file.length(); - int fileLength = 0; - long nWrite = 0; - // FIXME why sync? - try { - synchronized (fc) { - while (fileLength != l) { - if (handler.isFailed()) { - LOGGER.debug("Stalled error"); - throw new FileUploadStalledException(); - } - try { - nWrite = fc.transferTo(fileLength, l, target); - - if (nWrite == 0) { - LOGGER.info("Waiting for writing..."); - try { - fc.wait(50); - } catch (InterruptedException e) { - LOGGER.trace(e.getMessage(), e); - } - } else { - handler.writeHappened(); - } - } catch (IOException ex) { - String message = ex.getMessage(); - - // http://bugs.sun.com/view_bug.do?bug_id=5103988 - if (message != null && message.equalsIgnoreCase("Resource temporarily unavailable")) { - try { - fc.wait(1000); - } catch (InterruptedException e) { - LOGGER.trace(e.getMessage(), e); - } - LOGGER.warn("Experiencing NIO issue http://bugs.sun.com/view_bug.do?bug_id=5103988. Retrying"); - continue; - } else { - throw ex; - } - } - fileLength += nWrite; - } - } - } finally { - handler.completed(); - raf.close(); - } - - length += MultipartUtils.writeBytesToChannel(target, generateFileEnd()); - - return length; - } -} diff --git a/api/src/main/java/org/asynchttpclient/multipart/FilePartStallHandler.java b/api/src/main/java/org/asynchttpclient/multipart/FilePartStallHandler.java deleted file mode 100644 index ed89723027..0000000000 --- a/api/src/main/java/org/asynchttpclient/multipart/FilePartStallHandler.java +++ /dev/null @@ -1,61 +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.multipart; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * @author Gail Hernandez - */ -public class FilePartStallHandler extends TimerTask { - public FilePartStallHandler(long waitTime, AbstractFilePart filePart) { - _waitTime = waitTime; - _failed = false; - _written = false; - } - - public void completed() { - if (_waitTime > 0) { - _timer.cancel(); - } - } - - public boolean isFailed() { - return _failed; - } - - public void run() { - if (!_written) { - _failed = true; - _timer.cancel(); - } - _written = false; - } - - public void start() { - if (_waitTime > 0) { - _timer = new Timer(); - _timer.scheduleAtFixedRate(this, _waitTime, _waitTime); - } - } - - public void writeHappened() { - _written = true; - } - - private long _waitTime; - private Timer _timer; - private boolean _failed; - private boolean _written; -} diff --git a/api/src/main/java/org/asynchttpclient/multipart/FileUploadStalledException.java b/api/src/main/java/org/asynchttpclient/multipart/FileUploadStalledException.java deleted file mode 100644 index 031f9354a7..0000000000 --- a/api/src/main/java/org/asynchttpclient/multipart/FileUploadStalledException.java +++ /dev/null @@ -1,22 +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.multipart; - -import java.io.IOException; - -/** - * @author Gail Hernandez - */ -@SuppressWarnings("serial") -public class FileUploadStalledException extends IOException { -} diff --git a/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java b/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java deleted file mode 100644 index 1b732b9390..0000000000 --- a/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java +++ /dev/null @@ -1,240 +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.multipart; - -import org.asynchttpclient.RandomAccessBody; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.WritableByteChannel; -import java.util.ArrayList; -import java.util.List; - -public class MultipartBody implements RandomAccessBody { - - private final static Logger LOGGER = LoggerFactory.getLogger(MultipartBody.class); - - private final byte[] boundary; - private final long contentLength; - private final String contentType; - private final List parts; - private final List pendingOpenFiles = new ArrayList(); - - private boolean transfertDone = false; - - private int currentPart = 0; - private byte[] currentBytes; - private int currentBytesPosition = -1; - private boolean doneWritingParts = false; - private FileLocation fileLocation = FileLocation.NONE; - private FileChannel currentFileChannel; - - enum FileLocation { - NONE, START, MIDDLE, END - } - - public MultipartBody(List parts, String contentType, long contentLength, byte[] boundary) { - this.boundary = boundary; - this.contentLength = contentLength; - this.contentType = contentType; - this.parts = parts; - } - - public void close() throws IOException { - for (RandomAccessFile file : pendingOpenFiles) { - file.close(); - } - } - - public long getContentLength() { - return contentLength; - } - - public String getContentType() { - return contentType; - } - - // RandomAccessBody API, suited for HTTP but not for HTTPS - public long transferTo(long position, WritableByteChannel target) throws IOException { - - long overallLength = 0; - - if (transfertDone) { - return contentLength; - } - - for (Part part : parts) { - overallLength += part.write(target, boundary); - } - - overallLength += MultipartUtils.writeBytesToChannel(target, MultipartUtils.getMessageEnd(boundary)); - - transfertDone = true; - - return overallLength; - } - - // Regular Body API - public long read(ByteBuffer buffer) throws IOException { - try { - int overallLength = 0; - - int maxLength = buffer.remaining(); - - if (currentPart == parts.size() && transfertDone) { - return -1; - } - - boolean full = false; - - while (!full && !doneWritingParts) { - Part part = null; - - if (currentPart < parts.size()) { - part = parts.get(currentPart); - } - if (currentFileChannel != null) { - overallLength += writeCurrentFile(buffer); - full = overallLength == maxLength; - - } else if (currentBytesPosition > -1) { - overallLength += writeCurrentBytes(buffer, maxLength - overallLength); - full = overallLength == maxLength; - - if (currentPart == parts.size() && currentBytesFullyRead()) { - doneWritingParts = true; - } - - } else if (part instanceof StringPart) { - StringPart stringPart = (StringPart) part; - // set new bytes, not full, so will loop to writeCurrentBytes above - initializeCurrentBytes(stringPart.getBytes(boundary)); - currentPart++; - - } else if (part instanceof AbstractFilePart) { - - AbstractFilePart filePart = (AbstractFilePart) part; - - switch (fileLocation) { - case NONE: - // set new bytes, not full, so will loop to writeCurrentBytes above - initializeCurrentBytes(filePart.generateFileStart(boundary)); - fileLocation = FileLocation.START; - break; - case START: - // set current file channel so code above executes first - initializeFileBody(filePart); - fileLocation = FileLocation.MIDDLE; - break; - case MIDDLE: - initializeCurrentBytes(filePart.generateFileEnd()); - fileLocation = FileLocation.END; - break; - case END: - currentPart++; - fileLocation = FileLocation.NONE; - if (currentPart == parts.size()) { - doneWritingParts = true; - } - } - } - } - - if (doneWritingParts) { - if (currentBytesPosition == -1) { - initializeCurrentBytes(MultipartUtils.getMessageEnd(boundary)); - } - - if (currentBytesPosition > -1) { - overallLength += writeCurrentBytes(buffer, maxLength - overallLength); - - if (currentBytesFullyRead()) { - currentBytes = null; - currentBytesPosition = -1; - transfertDone = true; - } - } - } - return overallLength; - - } catch (Exception e) { - LOGGER.error("Read exception", e); - return 0; - } - } - - private boolean currentBytesFullyRead() { - return currentBytes == null || currentBytesPosition >= currentBytes.length - 1; - } - - private void initializeFileBody(AbstractFilePart part) throws IOException { - - if (part instanceof FilePart) { - RandomAccessFile raf = new RandomAccessFile(FilePart.class.cast(part).getFile(), "r"); - pendingOpenFiles.add(raf); - currentFileChannel = raf.getChannel(); - - } else if (part instanceof ByteArrayPart) { - initializeCurrentBytes(ByteArrayPart.class.cast(part).getBytes()); - - } else { - throw new IllegalArgumentException("Unknow AbstractFilePart type"); - } - } - - private void initializeCurrentBytes(byte[] bytes) throws IOException { - currentBytes = bytes; - currentBytesPosition = 0; - } - - private int writeCurrentFile(ByteBuffer buffer) throws IOException { - - int read = currentFileChannel.read(buffer); - - if (currentFileChannel.position() == currentFileChannel.size()) { - - currentFileChannel.close(); - currentFileChannel = null; - - int currentFile = pendingOpenFiles.size() - 1; - pendingOpenFiles.get(currentFile).close(); - pendingOpenFiles.remove(currentFile); - } - - return read; - } - - private int writeCurrentBytes(ByteBuffer buffer, int length) throws IOException { - - int available = currentBytes.length - currentBytesPosition; - - int writeLength = Math.min(available, length); - - if (writeLength > 0) { - buffer.put(currentBytes, currentBytesPosition, writeLength); - - if (available <= length) { - currentBytesPosition = -1; - currentBytes = null; - } else { - currentBytesPosition += writeLength; - } - } - - return writeLength; - } -} diff --git a/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java b/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java deleted file mode 100644 index d1f3ace5dc..0000000000 --- a/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.multipart; - -import static org.asynchttpclient.multipart.Part.CRLF_BYTES; -import static org.asynchttpclient.multipart.Part.EXTRA_BYTES; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.util.StandardCharsets; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.nio.channels.SocketChannel; -import java.nio.channels.WritableByteChannel; -import java.util.List; -import java.util.Random; -import java.util.Set; - -public class MultipartUtils { - - private static final Logger LOGGER = LoggerFactory.getLogger(MultipartUtils.class); - - /** - * The Content-Type for multipart/form-data. - */ - private static final String MULTIPART_FORM_CONTENT_TYPE = "multipart/form-data"; - - /** - * The pool of ASCII chars to be used for generating a multipart boundary. - */ - private static byte[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - .getBytes(StandardCharsets.US_ASCII); - - private MultipartUtils() { - } - - /** - * Creates a new multipart entity containing the given parts. - * - * @param parts - * The parts to include. - */ - public static MultipartBody newMultipartBody(List parts, FluentCaseInsensitiveStringsMap requestHeaders) { - if (parts == null) { - throw new NullPointerException("parts"); - } - - byte[] multipartBoundary; - String contentType; - - String contentTypeHeader = requestHeaders.getFirstValue("Content-Type"); - if (isNonEmpty(contentTypeHeader)) { - int boundaryLocation = contentTypeHeader.indexOf("boundary="); - if (boundaryLocation != -1) { - // boundary defined in existing Content-Type - contentType = contentTypeHeader; - multipartBoundary = (contentTypeHeader.substring(boundaryLocation + "boundary=".length()).trim()) - .getBytes(StandardCharsets.US_ASCII); - } else { - // generate boundary and append it to existing Content-Type - multipartBoundary = generateMultipartBoundary(); - contentType = computeContentType(contentTypeHeader, multipartBoundary); - } - } else { - multipartBoundary = generateMultipartBoundary(); - contentType = computeContentType(MULTIPART_FORM_CONTENT_TYPE, multipartBoundary); - } - - long contentLength = getLengthOfParts(parts, multipartBoundary); - - return new MultipartBody(parts, contentType, contentLength, multipartBoundary); - } - - private static byte[] generateMultipartBoundary() { - Random rand = new Random(); - byte[] bytes = new byte[rand.nextInt(11) + 30]; // a random size from 30 to 40 - for (int i = 0; i < bytes.length; i++) { - bytes[i] = MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]; - } - return bytes; - } - - private static String computeContentType(String base, byte[] multipartBoundary) { - StringBuilder buffer = new StringBuilder(base); - if (!base.endsWith(";")) - buffer.append(";"); - return buffer.append(" boundary=").append(new String(multipartBoundary, StandardCharsets.US_ASCII)).toString(); - } - - public static long writeBytesToChannel(WritableByteChannel target, byte[] bytes) throws IOException { - - int written = 0; - int maxSpin = 0; - ByteBuffer message = ByteBuffer.wrap(bytes); - - if (target instanceof SocketChannel) { - final Selector selector = Selector.open(); - try { - final SocketChannel channel = (SocketChannel) target; - channel.register(selector, SelectionKey.OP_WRITE); - - while (written < bytes.length) { - selector.select(1000); - maxSpin++; - final Set selectedKeys = selector.selectedKeys(); - - for (SelectionKey key : selectedKeys) { - if (key.isWritable()) { - written += target.write(message); - maxSpin = 0; - } - } - if (maxSpin >= 10) { - throw new IOException("Unable to write on channel " + target); - } - } - } finally { - selector.close(); - } - } else { - while ((target.isOpen()) && (written < bytes.length)) { - long nWrite = target.write(message); - written += nWrite; - if (nWrite == 0 && maxSpin++ < 10) { - LOGGER.info("Waiting for writing..."); - try { - bytes.wait(1000); - } catch (InterruptedException e) { - LOGGER.trace(e.getMessage(), e); - } - } else { - if (maxSpin >= 10) { - throw new IOException("Unable to write on channel " + target); - } - maxSpin = 0; - } - } - } - return written; - } - - public static byte[] getMessageEnd(byte[] partBoundary) throws IOException { - - if (!isNonEmpty(partBoundary)) - throw new IllegalArgumentException("partBoundary may not be empty"); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - OutputStreamPartVisitor visitor = new OutputStreamPartVisitor(out); - visitor.withBytes(EXTRA_BYTES); - visitor.withBytes(partBoundary); - visitor.withBytes(EXTRA_BYTES); - visitor.withBytes(CRLF_BYTES); - - return out.toByteArray(); - } - - public static long getLengthOfParts(List parts, byte[] partBoundary) { - - try { - if (parts == null) { - throw new NullPointerException("parts"); - } - long total = 0; - for (Part part : parts) { - long l = part.length(partBoundary); - if (l < 0) { - return -1; - } - total += l; - } - total += EXTRA_BYTES.length; - total += partBoundary.length; - total += EXTRA_BYTES.length; - total += CRLF_BYTES.length; - return total; - } catch (Exception e) { - LOGGER.error("An exception occurred while getting the length of the parts", e); - return 0L; - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/multipart/OutputStreamPartVisitor.java b/api/src/main/java/org/asynchttpclient/multipart/OutputStreamPartVisitor.java deleted file mode 100644 index 518962f8d9..0000000000 --- a/api/src/main/java/org/asynchttpclient/multipart/OutputStreamPartVisitor.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.multipart; - -import java.io.IOException; -import java.io.OutputStream; - -public class OutputStreamPartVisitor implements PartVisitor { - - private final OutputStream out; - - public OutputStreamPartVisitor(OutputStream out) { - this.out = out; - } - - @Override - public void withBytes(byte[] bytes) throws IOException { - out.write(bytes); - } - - @Override - public void withByte(byte b) throws IOException { - out.write(b); - } - - public OutputStream getOutputStream() { - return out; - } -} diff --git a/api/src/main/java/org/asynchttpclient/multipart/Part.java b/api/src/main/java/org/asynchttpclient/multipart/Part.java deleted file mode 100644 index 4a5a2d90b8..0000000000 --- a/api/src/main/java/org/asynchttpclient/multipart/Part.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.multipart; - -import static org.asynchttpclient.util.StandardCharsets.US_ASCII; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.channels.WritableByteChannel; -import java.nio.charset.Charset; - -public interface Part { - - /** - * Carriage return/linefeed as a byte array - */ - byte[] CRLF_BYTES = "\r\n".getBytes(US_ASCII); - - /** - * Content dispostion as a byte - */ - byte QUOTE_BYTE = '\"'; - - /** - * Extra characters as a byte array - */ - byte[] EXTRA_BYTES = "--".getBytes(US_ASCII); - - /** - * Content dispostion as a byte array - */ - byte[] CONTENT_DISPOSITION_BYTES = "Content-Disposition: ".getBytes(US_ASCII); - - /** - * form-data as a byte array - */ - byte[] FORM_DATA_DISPOSITION_TYPE_BYTES = "form-data".getBytes(US_ASCII); - - /** - * name as a byte array - */ - byte[] NAME_BYTES = "; name=".getBytes(US_ASCII); - - /** - * Content type header as a byte array - */ - byte[] CONTENT_TYPE_BYTES = "Content-Type: ".getBytes(US_ASCII); - - /** - * Content charset as a byte array - */ - byte[] CHARSET_BYTES = "; charset=".getBytes(US_ASCII); - - /** - * Content type header as a byte array - */ - byte[] CONTENT_TRANSFER_ENCODING_BYTES = "Content-Transfer-Encoding: ".getBytes(US_ASCII); - - /** - * Content type header as a byte array - */ - byte[] CONTENT_ID_BYTES = "Content-ID: ".getBytes(US_ASCII); - - /** - * Return the name of this part. - * - * @return The name. - */ - String getName(); - - /** - * Returns the content type of this part. - * - * @return the content type, or null to exclude the content type header - */ - String getContentType(); - - /** - * Return the character encoding of this part. - * - * @return the character encoding, or null to exclude the character encoding header - */ - Charset getCharset(); - - /** - * Return the transfer encoding of this part. - * - * @return the transfer encoding, or null to exclude the transfer encoding header - */ - String getTransferEncoding(); - - /** - * Return the content ID of this part. - * - * @return the content ID, or null to exclude the content ID header - */ - String getContentId(); - - /** - * Gets the disposition-type to be used in Content-Disposition header - * - * @return the disposition-type - */ - String getDispositionType(); - - /** - * Write all the data to the output stream. If you override this method make sure to override #length() as well - * - * @param out - * The output stream - * @param boundary - * the boundary - * @throws IOException - * If an IO problem occurs. - */ - void write(OutputStream out, byte[] boundary) throws IOException; - - /** - * Return the full length of all the data. If you override this method make sure to override #send(OutputStream) as well - * - * @return long The length. - */ - long length(byte[] boundary); - - long write(WritableByteChannel target, byte[] boundary) throws IOException; -} diff --git a/api/src/main/java/org/asynchttpclient/multipart/PartBase.java b/api/src/main/java/org/asynchttpclient/multipart/PartBase.java deleted file mode 100644 index a241df72a9..0000000000 --- a/api/src/main/java/org/asynchttpclient/multipart/PartBase.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.multipart; - -import static org.asynchttpclient.util.StandardCharsets.US_ASCII; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.Charset; - -public abstract class PartBase implements Part { - - /** - * The name of the form field, part of the Content-Disposition header - */ - private final String name; - - /** - * The main part of the Content-Type header - */ - private final String contentType; - - /** - * The charset (part of Content-Type header) - */ - private final Charset charset; - - /** - * The Content-Transfer-Encoding header value. - */ - private final String transferEncoding; - - /** - * The Content-Id - */ - private final String contentId; - - /** - * The disposition type (part of Content-Disposition) - */ - private String dispositionType; - - /** - * Constructor. - * - * @param name The name of the part, or null - * @param contentType The content type, or null - * @param charset The character encoding, or null - * @param contentId The content id, or null - * @param transferEncoding The transfer encoding, or null - */ - public PartBase(String name, String contentType, Charset charset, String contentId, String transferEncoding) { - this.name = name; - this.contentType = contentType; - this.charset = charset; - this.contentId = contentId; - this.transferEncoding = transferEncoding; - } - - protected void visitStart(PartVisitor visitor, byte[] boundary) throws IOException { - visitor.withBytes(EXTRA_BYTES); - visitor.withBytes(boundary); - } - - protected void visitDispositionHeader(PartVisitor visitor) throws IOException { - visitor.withBytes(CRLF_BYTES); - visitor.withBytes(CONTENT_DISPOSITION_BYTES); - visitor.withBytes(getDispositionType() != null ? getDispositionType().getBytes(US_ASCII) : FORM_DATA_DISPOSITION_TYPE_BYTES); - if (getName() != null) { - visitor.withBytes(NAME_BYTES); - visitor.withByte(QUOTE_BYTE); - visitor.withBytes(getName().getBytes(US_ASCII)); - visitor.withByte(QUOTE_BYTE); - } - } - - protected void visitContentTypeHeader(PartVisitor visitor) throws IOException { - String contentType = getContentType(); - if (contentType != null) { - visitor.withBytes(CRLF_BYTES); - visitor.withBytes(CONTENT_TYPE_BYTES); - visitor.withBytes(contentType.getBytes(US_ASCII)); - Charset charSet = getCharset(); - if (charSet != null) { - visitor.withBytes(CHARSET_BYTES); - visitor.withBytes(charset.name().getBytes(US_ASCII)); - } - } - } - - protected void visitTransferEncodingHeader(PartVisitor visitor) throws IOException { - String transferEncoding = getTransferEncoding(); - if (transferEncoding != null) { - visitor.withBytes(CRLF_BYTES); - visitor.withBytes(CONTENT_TRANSFER_ENCODING_BYTES); - visitor.withBytes(transferEncoding.getBytes(US_ASCII)); - } - } - - protected void visitContentIdHeader(PartVisitor visitor) throws IOException { - String contentId = getContentId(); - if (contentId != null) { - visitor.withBytes(CRLF_BYTES); - visitor.withBytes(CONTENT_ID_BYTES); - visitor.withBytes(contentId.getBytes(US_ASCII)); - } - } - - protected void visitEndOfHeader(PartVisitor visitor) throws IOException { - visitor.withBytes(CRLF_BYTES); - visitor.withBytes(CRLF_BYTES); - } - - protected void visitEnd(PartVisitor visitor) throws IOException { - visitor.withBytes(CRLF_BYTES); - } - - protected abstract long getDataLength(); - - protected abstract void sendData(OutputStream out) throws IOException; - - /** - * Write all the data to the output stream. If you override this method make sure to override #length() as well - * - * @param out - * The output stream - * @param boundary - * the boundary - * @throws IOException - * If an IO problem occurs. - */ - public void write(OutputStream out, byte[] boundary) throws IOException { - - OutputStreamPartVisitor visitor = new OutputStreamPartVisitor(out); - - visitStart(visitor, boundary); - visitDispositionHeader(visitor); - visitContentTypeHeader(visitor); - visitTransferEncodingHeader(visitor); - visitContentIdHeader(visitor); - visitEndOfHeader(visitor); - sendData(visitor.getOutputStream()); - visitEnd(visitor); - } - - /** - * Return the full length of all the data. If you override this method make sure to override #send(OutputStream) as well - * - * @return long The length. - */ - public long length(byte[] boundary) { - - long dataLength = getDataLength(); - try { - - if (dataLength < 0L) { - return -1L; - } else { - CounterPartVisitor visitor = new CounterPartVisitor(); - visitStart(visitor, boundary); - visitDispositionHeader(visitor); - visitContentTypeHeader(visitor); - visitTransferEncodingHeader(visitor); - visitContentIdHeader(visitor); - visitEndOfHeader(visitor); - visitEnd(visitor); - return dataLength + visitor.getCount(); - } - } catch (IOException e) { - // can't happen - throw new RuntimeException("IOException while computing length, WTF", e); - } - } - - public String toString() { - return new StringBuilder()// - .append(getClass().getSimpleName())// - .append(" name=").append(getName())// - .append(" contentType=").append(getContentType())// - .append(" charset=").append(getCharset())// - .append(" tranferEncoding=").append(getTransferEncoding())// - .append(" contentId=").append(getContentId())// - .append(" dispositionType=").append(getDispositionType())// - .toString(); - } - - @Override - public String getName() { - return this.name; - } - - @Override - public String getContentType() { - return this.contentType; - } - - @Override - public Charset getCharset() { - return this.charset; - } - - @Override - public String getTransferEncoding() { - return transferEncoding; - } - - @Override - public String getContentId() { - return contentId; - } - - @Override - public String getDispositionType() { - return dispositionType; - } - - public void setDispositionType(String dispositionType) { - this.dispositionType = dispositionType; - } -} diff --git a/api/src/main/java/org/asynchttpclient/multipart/PartVisitor.java b/api/src/main/java/org/asynchttpclient/multipart/PartVisitor.java deleted file mode 100644 index a7b8b84492..0000000000 --- a/api/src/main/java/org/asynchttpclient/multipart/PartVisitor.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.multipart; - -import java.io.IOException; - -public interface PartVisitor { - - void withBytes(byte[] bytes) throws IOException; - void withByte(byte b) throws IOException; -} diff --git a/api/src/main/java/org/asynchttpclient/multipart/StringPart.java b/api/src/main/java/org/asynchttpclient/multipart/StringPart.java deleted file mode 100644 index eb6b90b329..0000000000 --- a/api/src/main/java/org/asynchttpclient/multipart/StringPart.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.multipart; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.channels.WritableByteChannel; -import java.nio.charset.Charset; - -import org.asynchttpclient.util.StandardCharsets; - -public class StringPart extends PartBase { - - /** - * Default content encoding of string parameters. - */ - public static final String DEFAULT_CONTENT_TYPE = "text/plain"; - - /** - * Default charset of string parameters - */ - public static final Charset DEFAULT_CHARSET = StandardCharsets.US_ASCII; - - /** - * Default transfer encoding of string parameters - */ - public static final String DEFAULT_TRANSFER_ENCODING = "8bit"; - - /** - * Contents of this StringPart. - */ - private final byte[] content; - - private static Charset charsetOrDefault(Charset charset) { - return charset == null ? DEFAULT_CHARSET : charset; - } - - public StringPart(String name, String value, Charset charset) { - this(name, value, charset, null); - } - - /** - * Constructor. - * - * @param name - * The name of the part - * @param value - * the string to post - * @param charset - * the charset to be used to encode the string, if null the {@link #DEFAULT_CHARSET default} is used - * @param contentId - * the content id - */ - public StringPart(String name, String value, Charset charset, String contentId) { - - super(name, DEFAULT_CONTENT_TYPE, charsetOrDefault(charset), DEFAULT_TRANSFER_ENCODING, contentId); - if (value == null) - throw new NullPointerException("value"); - if (value.indexOf(0) != -1) - // See RFC 2048, 2.8. "8bit Data" - throw new IllegalArgumentException("NULs may not be present in string parts"); - content = value.getBytes(charsetOrDefault(charset)); - } - - /** - * Writes the data to the given OutputStream. - * - * @param out - * the OutputStream to write to - * @throws java.io.IOException - * if there is a write error - */ - protected void sendData(OutputStream out) throws IOException { - out.write(content); - } - - /** - * Return the length of the data. - * - * @return The length of the data. - */ - protected long getDataLength() { - return content.length; - } - - public byte[] getBytes(byte[] boundary) throws IOException { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - write(outputStream, boundary); - return outputStream.toByteArray(); - } - - @Override - public long write(WritableByteChannel target, byte[] boundary) throws IOException { - return MultipartUtils.writeBytesToChannel(target, getBytes(boundary)); - } -} diff --git a/api/src/main/java/org/asynchttpclient/ntlm/NTLMEngine.java b/api/src/main/java/org/asynchttpclient/ntlm/NTLMEngine.java deleted file mode 100644 index 687759f755..0000000000 --- a/api/src/main/java/org/asynchttpclient/ntlm/NTLMEngine.java +++ /dev/null @@ -1,1284 +0,0 @@ -/* - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file 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 - * - * 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. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.asynchttpclient.ntlm; - -import org.asynchttpclient.util.Base64; -import org.asynchttpclient.util.StandardCharsets; - -import javax.crypto.Cipher; -import javax.crypto.spec.SecretKeySpec; - -import java.io.UnsupportedEncodingException; -import java.security.Key; -import java.security.MessageDigest; -import java.util.Arrays; -import java.util.Locale; - -/** - * Provides an implementation for NTLMv1, NTLMv2, and NTLM2 Session forms of the NTLM - * authentication protocol. - * - * @since 4.1 - */ -public class NTLMEngine { - - // Flags we use - protected final static int FLAG_UNICODE_ENCODING = 0x00000001; - protected final static int FLAG_TARGET_DESIRED = 0x00000004; - protected final static int FLAG_NEGOTIATE_SIGN = 0x00000010; - protected final static int FLAG_NEGOTIATE_SEAL = 0x00000020; - protected final static int FLAG_NEGOTIATE_NTLM = 0x00000200; - protected final static int FLAG_NEGOTIATE_ALWAYS_SIGN = 0x00008000; - protected final static int FLAG_NEGOTIATE_NTLM2 = 0x00080000; - protected final static int FLAG_NEGOTIATE_128 = 0x20000000; - protected final static int FLAG_NEGOTIATE_KEY_EXCH = 0x40000000; - - /** - * Secure random generator - */ - private static final java.security.SecureRandom RND_GEN; - - static { - java.security.SecureRandom rnd = null; - try { - rnd = java.security.SecureRandom.getInstance("SHA1PRNG"); - } catch (Exception ignored) { - } - RND_GEN = rnd; - } - - /** - * Character encoding - */ - static final String DEFAULT_CHARSET = "ASCII"; - - /** - * The character set to use for encoding the credentials - */ - private String credentialCharset = DEFAULT_CHARSET; - - private static final byte[] NTLMSSP_BYTES = "NTLMSSP".getBytes(StandardCharsets.US_ASCII); - private static final byte[] MAGIC_CONSTANT = "KGS!@#$%".getBytes(StandardCharsets.US_ASCII); - - /** - * The signature string as bytes in the default encoding - */ - private static byte[] SIGNATURE; - - static { - byte[] bytesWithoutNull = new byte[0]; - bytesWithoutNull = NTLMSSP_BYTES; - SIGNATURE = new byte[bytesWithoutNull.length + 1]; - System.arraycopy(bytesWithoutNull, 0, SIGNATURE, 0, bytesWithoutNull.length); - SIGNATURE[bytesWithoutNull.length] = (byte) 0x00; - } - - public static final NTLMEngine INSTANCE = new NTLMEngine(); - - /** - * Returns the response for the given message. - * - * @param message the message that was received from the server. - * @param username the username to authenticate with. - * @param password the password to authenticate with. - * @param host The host. - * @param domain the NT domain to authenticate in. - * @return The response. - * @throws NTLMEngineException If the messages cannot be retrieved. - */ - final String getResponseFor(String message, String username, String password, String host, String domain) throws NTLMEngineException { - - final String response; - if (message == null || message.trim().length() == 0) { - response = getType1Message(host, domain); - } else { - Type2Message t2m = new Type2Message(message); - response = getType3Message(username, password, host, domain, t2m.getChallenge(), t2m.getFlags(), t2m.getTarget(), - t2m.getTargetInfo()); - } - return response; - } - - /** - * Creates the first message (type 1 message) in the NTLM authentication - * sequence. This message includes the user name, domain and host for the - * authentication session. - * - * @param host the computer name of the host requesting authentication. - * @param domain The domain to authenticate with. - * @return String the message to add to the HTTP request header. - */ - String getType1Message(String host, String domain) throws NTLMEngineException { - try { - return new Type1Message(domain, host).getResponse(); - } catch (UnsupportedEncodingException e) { - throw new NTLMEngineException("Unsupported encoding", e); - } - } - - /** - * Creates the type 3 message using the given server nonce. The type 3 - * message includes all the information for authentication, host, domain, - * 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 password The password. - * @param host The host that is originating the authentication request. - * @param domain The domain to authenticate within. - * @param nonce the 8 byte array the server sent. - * @return The type 3 message. - * @throws NTLMEngineException If {@encrypt(byte[],byte[])} fails. - */ - String getType3Message(String user, String password, String host, String domain, byte[] nonce, int type2Flags, String target, - byte[] targetInformation) throws NTLMEngineException { - try { - return new Type3Message(domain, host, user, password, nonce, type2Flags, target, targetInformation).getResponse(); - } catch (UnsupportedEncodingException e) { - throw new NTLMEngineException("Unsupported encoding", e); - } - } - - /** - * @return Returns the credentialCharset. - */ - String getCredentialCharset() { - return credentialCharset; - } - - /** - * @param credentialCharset The credentialCharset to set. - */ - void setCredentialCharset(String credentialCharset) { - this.credentialCharset = credentialCharset; - } - - /** - * Strip dot suffix from a name - */ - private static String stripDotSuffix(String value) { - int index = value.indexOf("."); - if (index != -1) - return value.substring(0, index); - return value; - } - - /** - * Convert host to standard form - */ - private static String convertHost(String host) { - return stripDotSuffix(host); - } - - /** - * Convert domain to standard form - */ - private static String convertDomain(String domain) { - return stripDotSuffix(domain); - } - - private static int readULong(byte[] src, int index) throws NTLMEngineException { - if (src.length < index + 4) - throw new NTLMEngineException("NTLM authentication - buffer too small for DWORD"); - return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8) | ((src[index + 2] & 0xff) << 16) | ((src[index + 3] & 0xff) << 24); - } - - private static int readUShort(byte[] src, int index) throws NTLMEngineException { - if (src.length < index + 2) - throw new NTLMEngineException("NTLM authentication - buffer too small for WORD"); - return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8); - } - - private static byte[] readSecurityBuffer(byte[] src, int index) throws NTLMEngineException { - int length = readUShort(src, index); - int offset = readULong(src, index + 4); - if (src.length < offset + length) - throw new NTLMEngineException("NTLM authentication - buffer too small for data item"); - byte[] buffer = new byte[length]; - System.arraycopy(src, offset, buffer, 0, length); - return buffer; - } - - /** - * Calculate a challenge block - */ - private static byte[] makeRandomChallenge() throws NTLMEngineException { - if (RND_GEN == null) { - throw new NTLMEngineException("Random generator not available"); - } - byte[] rval = new byte[8]; - synchronized (RND_GEN) { - RND_GEN.nextBytes(rval); - } - return rval; - } - - /** - * Calculate an NTLM2 challenge block - */ - private static byte[] makeNTLM2RandomChallenge() throws NTLMEngineException { - if (RND_GEN == null) { - throw new NTLMEngineException("Random generator not available"); - } - byte[] rval = new byte[24]; - synchronized (RND_GEN) { - RND_GEN.nextBytes(rval); - } - // 8-byte challenge, padded with zeros to 24 bytes. - Arrays.fill(rval, 8, 24, (byte) 0x00); - return rval; - } - - /** - * Calculates the LM Response for the given challenge, using the specified - * password. - * - * @param password The user's password. - * @param challenge The Type 2 challenge from the server. - * @return The LM Response. - */ - static byte[] getLMResponse(String password, byte[] challenge) throws NTLMEngineException { - byte[] lmHash = lmHash(password); - return lmResponse(lmHash, challenge); - } - - /** - * Calculates the NTLM Response for the given challenge, using the specified - * password. - * - * @param password The user's password. - * @param challenge The Type 2 challenge from the server. - * @return The NTLM Response. - */ - static byte[] getNTLMResponse(String password, byte[] challenge) throws NTLMEngineException { - byte[] ntlmHash = ntlmHash(password); - return lmResponse(ntlmHash, challenge); - } - - /** - * Calculates the NTLMv2 Response for the given challenge, using the - * specified authentication target, username, password, target information - * block, and client challenge. - * - * @param target The authentication target (i.e., domain). - * @param user The username. - * @param password The user's password. - * @param targetInformation The target information block from the Type 2 message. - * @param challenge The Type 2 challenge from the server. - * @param clientChallenge The random 8-byte client challenge. - * @return The NTLMv2 Response. - */ - static byte[] getNTLMv2Response(String target, String user, String password, byte[] challenge, byte[] clientChallenge, - byte[] targetInformation) throws NTLMEngineException { - byte[] ntlmv2Hash = ntlmv2Hash(target, user, password); - byte[] blob = createBlob(clientChallenge, targetInformation); - return lmv2Response(ntlmv2Hash, challenge, blob); - } - - /** - * Calculates the LMv2 Response for the given challenge, using the specified - * authentication target, username, password, and client challenge. - * - * @param target The authentication target (i.e., domain). - * @param user The username. - * @param password The user's password. - * @param challenge The Type 2 challenge from the server. - * @param clientChallenge The random 8-byte client challenge. - * @return The LMv2 Response. - */ - static byte[] getLMv2Response(String target, String user, String password, byte[] challenge, byte[] clientChallenge) - throws NTLMEngineException { - byte[] ntlmv2Hash = ntlmv2Hash(target, user, password); - return lmv2Response(ntlmv2Hash, challenge, clientChallenge); - } - - /** - * Calculates the NTLM2 Session Response for the given challenge, using the - * specified password and client challenge. - * - * @param password The user's password. - * @param challenge The Type 2 challenge from the server. - * @param clientChallenge The random 8-byte client challenge. - * @return The NTLM2 Session Response. This is placed in the NTLM response - * field of the Type 3 message; the LM response field contains the - * client challenge, null-padded to 24 bytes. - */ - static byte[] getNTLM2SessionResponse(String password, byte[] challenge, byte[] clientChallenge) throws NTLMEngineException { - try { - byte[] ntlmHash = ntlmHash(password); - - // Look up MD5 algorithm (was necessary on jdk 1.4.2) - // This used to be needed, but java 1.5.0_07 includes the MD5 - // algorithm (finally) - // Class x = Class.forName("gnu.crypto.hash.MD5"); - // Method updateMethod = x.getMethod("update",new - // Class[]{byte[].class}); - // Method digestMethod = x.getMethod("digest",new Class[0]); - // Object mdInstance = x.newInstance(); - // updateMethod.invoke(mdInstance,new Object[]{challenge}); - // updateMethod.invoke(mdInstance,new Object[]{clientChallenge}); - // byte[] digest = (byte[])digestMethod.invoke(mdInstance,new - // Object[0]); - - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.update(challenge); - md5.update(clientChallenge); - byte[] digest = md5.digest(); - - byte[] sessionHash = new byte[8]; - System.arraycopy(digest, 0, sessionHash, 0, 8); - return lmResponse(ntlmHash, sessionHash); - } catch (Exception e) { - if (e instanceof NTLMEngineException) - throw (NTLMEngineException) e; - throw new NTLMEngineException(e.getMessage(), e); - } - } - - /** - * Creates the LM Hash of the user's password. - * - * @param password The password. - * @return The LM Hash of the given password, used in the calculation of the - * LM Response. - */ - private static byte[] lmHash(String password) throws NTLMEngineException { - try { - byte[] oemPassword = password.toUpperCase(Locale.ENGLISH).getBytes(StandardCharsets.US_ASCII); - int length = Math.min(oemPassword.length, 14); - byte[] keyBytes = new byte[14]; - System.arraycopy(oemPassword, 0, keyBytes, 0, length); - Key lowKey = createDESKey(keyBytes, 0); - Key highKey = createDESKey(keyBytes, 7); - Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); - des.init(Cipher.ENCRYPT_MODE, lowKey); - byte[] lowHash = des.doFinal(MAGIC_CONSTANT); - des.init(Cipher.ENCRYPT_MODE, highKey); - byte[] highHash = des.doFinal(MAGIC_CONSTANT); - byte[] lmHash = new byte[16]; - System.arraycopy(lowHash, 0, lmHash, 0, 8); - System.arraycopy(highHash, 0, lmHash, 8, 8); - return lmHash; - } catch (Exception e) { - throw new NTLMEngineException(e.getMessage(), e); - } - } - - /** - * Creates the NTLM Hash of the user's password. - * - * @param password The password. - * @return The NTLM Hash of the given password, used in the calculation of - * the NTLM Response and the NTLMv2 and LMv2 Hashes. - */ - private static byte[] ntlmHash(String password) throws NTLMEngineException { - byte[] unicodePassword = password.getBytes(StandardCharsets.UNICODE_LITTLE_UNMARKED); - MD4 md4 = new MD4(); - md4.update(unicodePassword); - return md4.getOutput(); - } - - /** - * Creates the NTLMv2 Hash of the user's password. - * - * @param target The authentication target (i.e., domain). - * @param user The username. - * @param password The password. - * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 and LMv2 - * Responses. - */ - private static byte[] ntlmv2Hash(String target, String user, String password) throws NTLMEngineException { - byte[] ntlmHash = ntlmHash(password); - HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); - // Upper case username, mixed case target!! - hmacMD5.update(user.toUpperCase(Locale.ENGLISH).getBytes(StandardCharsets.UNICODE_LITTLE_UNMARKED)); - hmacMD5.update(target.getBytes(StandardCharsets.UNICODE_LITTLE_UNMARKED)); - return hmacMD5.getOutput(); - } - - /** - * Creates the LM Response from the given hash and Type 2 challenge. - * - * @param hash The LM or NTLM Hash. - * @param challenge The server challenge from the Type 2 message. - * @return The response (either LM or NTLM, depending on the provided hash). - */ - private static byte[] lmResponse(byte[] hash, byte[] challenge) throws NTLMEngineException { - try { - byte[] keyBytes = new byte[21]; - System.arraycopy(hash, 0, keyBytes, 0, 16); - Key lowKey = createDESKey(keyBytes, 0); - Key middleKey = createDESKey(keyBytes, 7); - Key highKey = createDESKey(keyBytes, 14); - Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); - des.init(Cipher.ENCRYPT_MODE, lowKey); - byte[] lowResponse = des.doFinal(challenge); - des.init(Cipher.ENCRYPT_MODE, middleKey); - byte[] middleResponse = des.doFinal(challenge); - des.init(Cipher.ENCRYPT_MODE, highKey); - byte[] highResponse = des.doFinal(challenge); - byte[] lmResponse = new byte[24]; - System.arraycopy(lowResponse, 0, lmResponse, 0, 8); - System.arraycopy(middleResponse, 0, lmResponse, 8, 8); - System.arraycopy(highResponse, 0, lmResponse, 16, 8); - return lmResponse; - } catch (Exception e) { - throw new NTLMEngineException(e.getMessage(), e); - } - } - - /** - * Creates the LMv2 Response from the given hash, client data, and Type 2 - * challenge. - * - * @param hash The NTLMv2 Hash. - * @param clientData The client data (blob or client challenge). - * @param challenge The server challenge from the Type 2 message. - * @return The response (either NTLMv2 or LMv2, depending on the client - * data). - */ - private static byte[] lmv2Response(byte[] hash, byte[] challenge, byte[] clientData) throws NTLMEngineException { - HMACMD5 hmacMD5 = new HMACMD5(hash); - hmacMD5.update(challenge); - hmacMD5.update(clientData); - byte[] mac = hmacMD5.getOutput(); - byte[] lmv2Response = new byte[mac.length + clientData.length]; - System.arraycopy(mac, 0, lmv2Response, 0, mac.length); - System.arraycopy(clientData, 0, lmv2Response, mac.length, clientData.length); - return lmv2Response; - } - - /** - * Creates the NTLMv2 blob from the given target information block and - * client challenge. - * - * @param targetInformation The target information block from the Type 2 message. - * @param clientChallenge The random 8-byte client challenge. - * @return The blob, used in the calculation of the NTLMv2 Response. - */ - private static byte[] createBlob(byte[] clientChallenge, byte[] targetInformation) { - byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 }; - byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; - byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; - long time = System.currentTimeMillis(); - time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch. - time *= 10000; // tenths of a microsecond. - // convert to little-endian byte array. - byte[] timestamp = new byte[8]; - for (int i = 0; i < 8; i++) { - timestamp[i] = (byte) time; - time >>>= 8; - } - byte[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + 8 + unknown1.length + targetInformation.length]; - int offset = 0; - System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length); - offset += blobSignature.length; - System.arraycopy(reserved, 0, blob, offset, reserved.length); - offset += reserved.length; - System.arraycopy(timestamp, 0, blob, offset, timestamp.length); - offset += timestamp.length; - System.arraycopy(clientChallenge, 0, blob, offset, 8); - offset += 8; - System.arraycopy(unknown1, 0, blob, offset, unknown1.length); - offset += unknown1.length; - System.arraycopy(targetInformation, 0, blob, offset, targetInformation.length); - return blob; - } - - /** - * Creates a DES encryption key from the given key material. - * - * @param bytes A byte array containing the DES key material. - * @param offset The offset in the given byte array at which the 7-byte key - * material starts. - * @return A DES encryption key created from the key material starting at - * the specified offset in the given byte array. - */ - private static Key createDESKey(byte[] bytes, int offset) { - byte[] keyBytes = new byte[7]; - System.arraycopy(bytes, offset, keyBytes, 0, 7); - byte[] material = new byte[8]; - material[0] = keyBytes[0]; - material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1); - material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2); - material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3); - material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4); - material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5); - material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6); - material[7] = (byte) (keyBytes[6] << 1); - oddParity(material); - return new SecretKeySpec(material, "DES"); - } - - /** - * Applies odd parity to the given byte array. - * - * @param bytes The data whose parity bits are to be adjusted for odd parity. - */ - private static void oddParity(byte[] bytes) { - for (int i = 0; i < bytes.length; i++) { - byte b = bytes[i]; - boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ (b >>> 4) ^ (b >>> 3) ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0; - if (needsParity) { - bytes[i] |= (byte) 0x01; - } else { - bytes[i] &= (byte) 0xfe; - } - } - } - - /** - * NTLM message generation, base class - */ - static class NTLMMessage { - /** - * The current response - */ - private byte[] messageContents = null; - - /** - * The current output position - */ - private int currentOutputPosition = 0; - - /** - * Constructor to use when message contents are not yet known - */ - NTLMMessage() { - } - - /** - * Constructor to use when message contents are known - */ - NTLMMessage(String messageBody, int expectedType) throws NTLMEngineException { - messageContents = Base64.decode(messageBody); - - // Look for NTLM message - if (messageContents.length < SIGNATURE.length) - throw new NTLMEngineException("NTLM message decoding error - packet too short"); - int i = 0; - while (i < SIGNATURE.length) { - if (messageContents[i] != SIGNATURE[i]) - throw new NTLMEngineException("NTLM message expected - instead got unrecognized bytes"); - i++; - } - - // Check to be sure there's a type 2 message indicator next - int type = readULong(SIGNATURE.length); - if (type != expectedType) - throw new NTLMEngineException("NTLM type " + Integer.toString(expectedType) + " message expected - instead got type " - + Integer.toString(type)); - - currentOutputPosition = messageContents.length; - } - - /** - * Get the length of the signature and flags, so calculations can adjust - * offsets accordingly. - */ - protected int getPreambleLength() { - return SIGNATURE.length + 4; - } - - /** - * Get the message length - */ - protected int getMessageLength() { - return currentOutputPosition; - } - - /** - * Read a byte from a position within the message buffer - */ - protected byte readByte(int position) throws NTLMEngineException { - if (messageContents.length < position + 1) - throw new NTLMEngineException("NTLM: Message too short"); - return messageContents[position]; - } - - /** - * Read a bunch of bytes from a position in the message buffer - */ - protected void readBytes(byte[] buffer, int position) throws NTLMEngineException { - if (messageContents.length < position + buffer.length) - throw new NTLMEngineException("NTLM: Message too short"); - System.arraycopy(messageContents, position, buffer, 0, buffer.length); - } - - /** - * Read a ushort from a position within the message buffer - */ - protected int readUShort(int position) throws NTLMEngineException { - return NTLMEngine.readUShort(messageContents, position); - } - - /** - * Read a ulong from a position within the message buffer - */ - protected int readULong(int position) throws NTLMEngineException { - return NTLMEngine.readULong(messageContents, position); - } - - /** - * Read a security buffer from a position within the message buffer - */ - protected byte[] readSecurityBuffer(int position) throws NTLMEngineException { - return NTLMEngine.readSecurityBuffer(messageContents, position); - } - - /** - * Prepares the object to create a response of the given length. - * - * @param maxlength the maximum length of the response to prepare, not - * including the type and the signature (which this method - * adds). - */ - protected void prepareResponse(int maxlength, int messageType) { - messageContents = new byte[maxlength]; - currentOutputPosition = 0; - addBytes(SIGNATURE); - addULong(messageType); - } - - /** - * Adds the given byte to the response. - * - * @param b the byte to add. - */ - protected void addByte(byte b) { - messageContents[currentOutputPosition] = b; - currentOutputPosition++; - } - - /** - * Adds the given bytes to the response. - * - * @param bytes the bytes to add. - */ - protected void addBytes(byte[] bytes) { - for (int i = 0; i < bytes.length; i++) { - messageContents[currentOutputPosition] = bytes[i]; - currentOutputPosition++; - } - } - - /** - * Adds a USHORT to the response - */ - protected void addUShort(int value) { - addByte((byte) (value & 0xff)); - addByte((byte) (value >> 8 & 0xff)); - } - - /** - * Adds a ULong to the response - */ - protected void addULong(int value) { - addByte((byte) (value & 0xff)); - addByte((byte) (value >> 8 & 0xff)); - addByte((byte) (value >> 16 & 0xff)); - addByte((byte) (value >> 24 & 0xff)); - } - - /** - * Returns the response that has been generated after shrinking the - * array if required and base64 encodes the response. - * - * @return The response as above. - */ - String getResponse() throws UnsupportedEncodingException { - byte[] resp; - if (messageContents.length > currentOutputPosition) { - byte[] tmp = new byte[currentOutputPosition]; - for (int i = 0; i < currentOutputPosition; i++) { - tmp[i] = messageContents[i]; - } - resp = tmp; - } else { - resp = messageContents; - } - return Base64.encode(resp); - } - - } - - /** - * Type 1 message assembly class - */ - static class Type1Message extends NTLMMessage { - protected byte[] hostBytes; - protected byte[] domainBytes; - - /** - * Constructor. Include the arguments the message will need - */ - Type1Message(String domain, String host) throws NTLMEngineException { - super(); - // Strip off domain name from the host! - host = convertHost(host); - // Use only the base domain name! - domain = convertDomain(domain); - - hostBytes = host.getBytes(StandardCharsets.UNICODE_LITTLE_UNMARKED); - domainBytes = domain.toUpperCase(Locale.ENGLISH).getBytes(StandardCharsets.UNICODE_LITTLE_UNMARKED); - } - - /** - * Getting the response involves building the message before returning - * it - */ - @Override - String getResponse() throws UnsupportedEncodingException { - // Now, build the message. Calculate its length first, including - // signature or type. - int finalLength = 32 + hostBytes.length + domainBytes.length; - - // Set up the response. This will initialize the signature, message - // type, and flags. - prepareResponse(finalLength, 1); - - // Flags. These are the complete set of flags we support. - addULong(FLAG_NEGOTIATE_NTLM | FLAG_NEGOTIATE_NTLM2 | FLAG_NEGOTIATE_SIGN | FLAG_NEGOTIATE_SEAL | - /* - * FLAG_NEGOTIATE_ALWAYS_SIGN | FLAG_NEGOTIATE_KEY_EXCH | - */ - FLAG_UNICODE_ENCODING | FLAG_TARGET_DESIRED | FLAG_NEGOTIATE_128); - - // Domain length (two times). - addUShort(domainBytes.length); - addUShort(domainBytes.length); - - // Domain offset. - addULong(hostBytes.length + 32); - - // Host length (two times). - addUShort(hostBytes.length); - addUShort(hostBytes.length); - - // Host offset (always 32). - addULong(32); - - // Host String. - addBytes(hostBytes); - - // Domain String. - addBytes(domainBytes); - - return super.getResponse(); - } - - } - - /** - * Type 2 message class - */ - static class Type2Message extends NTLMMessage { - protected byte[] challenge; - protected String target; - protected byte[] targetInfo; - protected int flags; - - Type2Message(String message) throws NTLMEngineException { - super(message, 2); - - // 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. - challenge = new byte[8]; - readBytes(challenge, 24); - - flags = readULong(20); - if ((flags & FLAG_UNICODE_ENCODING) == 0) - throw new NTLMEngineException("NTLM type 2 message has flags that make no sense: " + Integer.toString(flags)); - // Do the target! - target = null; - // The TARGET_DESIRED flag is said to not have understood semantics - // in Type2 messages, so use the length of the packet to decide - // how to proceed instead - if (getMessageLength() >= 12 + 8) { - byte[] bytes = readSecurityBuffer(12); - if (bytes.length != 0) { - target = new String(bytes, StandardCharsets.UNICODE_LITTLE_UNMARKED); - } - } - - // Do the target info! - targetInfo = null; - // TARGET_DESIRED flag cannot be relied on, so use packet length - if (getMessageLength() >= 40 + 8) { - byte[] bytes = readSecurityBuffer(40); - if (bytes.length != 0) { - targetInfo = bytes; - } - } - } - - /** - * Retrieve the challenge - */ - byte[] getChallenge() { - return challenge; - } - - /** - * Retrieve the target - */ - String getTarget() { - return target; - } - - /** - * Retrieve the target info - */ - byte[] getTargetInfo() { - return targetInfo; - } - - /** - * Retrieve the response flags - */ - int getFlags() { - return flags; - } - - } - - /** - * Type 3 message assembly class - */ - static class Type3Message extends NTLMMessage { - // Response flags from the type2 message - protected int type2Flags; - - protected byte[] domainBytes; - protected byte[] hostBytes; - protected byte[] userBytes; - - protected byte[] lmResp; - protected byte[] ntResp; - - /** - * Constructor. Pass the arguments we will need - */ - Type3Message(String domain, String host, String user, String password, byte[] nonce, int type2Flags, String target, - byte[] targetInformation) throws NTLMEngineException { - // Save the flags - this.type2Flags = type2Flags; - - // Strip off domain name from the host! - host = convertHost(host); - // Use only the base domain name! - domain = convertDomain(domain); - - // Use the new code to calculate the responses, including v2 if that - // seems warranted. - try { - if (targetInformation != null && target != null) { - byte[] clientChallenge = makeRandomChallenge(); - ntResp = getNTLMv2Response(target, user, password, nonce, clientChallenge, targetInformation); - lmResp = getLMv2Response(target, user, password, nonce, clientChallenge); - } else { - if ((type2Flags & FLAG_NEGOTIATE_NTLM2) != 0) { - // NTLM2 session stuff is requested - byte[] clientChallenge = makeNTLM2RandomChallenge(); - - ntResp = getNTLM2SessionResponse(password, nonce, clientChallenge); - lmResp = clientChallenge; - - // All the other flags we send (signing, sealing, key - // exchange) are supported, but they don't do anything - // at all in an - // NTLM2 context! So we're done at this point. - } else { - ntResp = getNTLMResponse(password, nonce); - lmResp = getLMResponse(password, nonce); - } - } - } catch (NTLMEngineException e) { - // This likely means we couldn't find the MD4 hash algorithm - - // fail back to just using LM - ntResp = new byte[0]; - lmResp = getLMResponse(password, nonce); - } - - domainBytes = domain.toUpperCase(Locale.ENGLISH).getBytes(StandardCharsets.UNICODE_LITTLE_UNMARKED); - hostBytes = host.getBytes(StandardCharsets.UNICODE_LITTLE_UNMARKED); - userBytes = user.getBytes(StandardCharsets.UNICODE_LITTLE_UNMARKED); - } - - /** - * Assemble the response - */ - @Override - String getResponse() throws UnsupportedEncodingException { - int ntRespLen = ntResp.length; - int lmRespLen = lmResp.length; - - int domainLen = domainBytes.length; - int hostLen = hostBytes.length; - int userLen = userBytes.length; - - // Calculate the layout within the packet - int lmRespOffset = 64; - int ntRespOffset = lmRespOffset + lmRespLen; - int domainOffset = ntRespOffset + ntRespLen; - int userOffset = domainOffset + domainLen; - int hostOffset = userOffset + userLen; - int finalLength = hostOffset + hostLen; - - // Start the response. Length includes signature and type - prepareResponse(finalLength, 3); - - // LM Resp Length (twice) - addUShort(lmRespLen); - addUShort(lmRespLen); - - // LM Resp Offset - addULong(lmRespOffset); - - // NT Resp Length (twice) - addUShort(ntRespLen); - addUShort(ntRespLen); - - // NT Resp Offset - addULong(ntRespOffset); - - // Domain length (twice) - addUShort(domainLen); - addUShort(domainLen); - - // Domain offset. - addULong(domainOffset); - - // User Length (twice) - addUShort(userLen); - addUShort(userLen); - - // User offset - addULong(userOffset); - - // Host length (twice) - addUShort(hostLen); - addUShort(hostLen); - - // Host offset - addULong(hostOffset); - - // 4 bytes of zeros - not sure what this is - addULong(0); - - // Message length - addULong(finalLength); - - // Flags. Currently: NEGOTIATE_NTLM + UNICODE_ENCODING + - // TARGET_DESIRED + NEGOTIATE_128 - addULong(FLAG_NEGOTIATE_NTLM | FLAG_UNICODE_ENCODING | FLAG_TARGET_DESIRED | FLAG_NEGOTIATE_128 - | (type2Flags & FLAG_NEGOTIATE_NTLM2) | (type2Flags & FLAG_NEGOTIATE_SIGN) | (type2Flags & FLAG_NEGOTIATE_SEAL) - | (type2Flags & FLAG_NEGOTIATE_KEY_EXCH) | (type2Flags & FLAG_NEGOTIATE_ALWAYS_SIGN)); - - // Add the actual data - addBytes(lmResp); - addBytes(ntResp); - addBytes(domainBytes); - addBytes(userBytes); - addBytes(hostBytes); - - return super.getResponse(); - } - } - - static void writeULong(byte[] buffer, int value, int offset) { - buffer[offset] = (byte) (value & 0xff); - buffer[offset + 1] = (byte) (value >> 8 & 0xff); - buffer[offset + 2] = (byte) (value >> 16 & 0xff); - buffer[offset + 3] = (byte) (value >> 24 & 0xff); - } - - static int F(int x, int y, int z) { - return ((x & y) | (~x & z)); - } - - static int G(int x, int y, int z) { - return ((x & y) | (x & z) | (y & z)); - } - - static int H(int x, int y, int z) { - return (x ^ y ^ z); - } - - static int rotintlft(int val, int numbits) { - return ((val << numbits) | (val >>> (32 - numbits))); - } - - /** - * Cryptography support - MD4. The following class was based loosely on the - * RFC and on code found at http://www.cs.umd.edu/~harry/jotp/src/md.java. - * Code correctness was verified by looking at MD4.java from the jcifs - * library (http://jcifs.samba.org). It was massaged extensively to the - * final form found here by Karl Wright (kwright@metacarta.com). - */ - static class MD4 { - protected int A = 0x67452301; - protected int B = 0xefcdab89; - protected int C = 0x98badcfe; - protected int D = 0x10325476; - protected long count = 0L; - protected byte[] dataBuffer = new byte[64]; - - MD4() { - } - - void update(byte[] input) { - // We always deal with 512 bits at a time. Correspondingly, there is - // a buffer 64 bytes long that we write data into until it gets - // full. - int curBufferPos = (int) (count & 63L); - int inputIndex = 0; - while (input.length - inputIndex + curBufferPos >= dataBuffer.length) { - // We have enough data to do the next step. Do a partial copy - // and a transform, updating inputIndex and curBufferPos - // accordingly - int transferAmt = dataBuffer.length - curBufferPos; - System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); - count += transferAmt; - curBufferPos = 0; - inputIndex += transferAmt; - processBuffer(); - } - - // If there's anything left, copy it into the buffer and leave it. - // We know there's not enough left to process. - if (inputIndex < input.length) { - int transferAmt = input.length - inputIndex; - System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); - count += transferAmt; - } - } - - byte[] getOutput() { - // Feed pad/length data into engine. This must round out the input - // to a multiple of 512 bits. - int bufferIndex = (int) (count & 63L); - int padLen = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex); - byte[] postBytes = new byte[padLen + 8]; - // Leading 0x80, specified amount of zero padding, then length in - // bits. - postBytes[0] = (byte) 0x80; - // Fill out the last 8 bytes with the length - for (int i = 0; i < 8; i++) { - postBytes[padLen + i] = (byte) ((count * 8) >>> (8 * i)); - } - - // Update the engine - update(postBytes); - - // Calculate final result - byte[] result = new byte[16]; - writeULong(result, A, 0); - writeULong(result, B, 4); - writeULong(result, C, 8); - writeULong(result, D, 12); - return result; - } - - protected void processBuffer() { - // Convert current buffer to 16 ulongs - int[] d = new int[16]; - - for (int i = 0; i < 16; i++) { - d[i] = (dataBuffer[i * 4] & 0xff) + ((dataBuffer[i * 4 + 1] & 0xff) << 8) + ((dataBuffer[i * 4 + 2] & 0xff) << 16) - + ((dataBuffer[i * 4 + 3] & 0xff) << 24); - } - - // Do a round of processing - int AA = A; - int BB = B; - int CC = C; - int DD = D; - round1(d); - round2(d); - round3(d); - A += AA; - B += BB; - C += CC; - D += DD; - - } - - protected void round1(int[] d) { - A = rotintlft((A + F(B, C, D) + d[0]), 3); - D = rotintlft((D + F(A, B, C) + d[1]), 7); - C = rotintlft((C + F(D, A, B) + d[2]), 11); - B = rotintlft((B + F(C, D, A) + d[3]), 19); - - A = rotintlft((A + F(B, C, D) + d[4]), 3); - D = rotintlft((D + F(A, B, C) + d[5]), 7); - C = rotintlft((C + F(D, A, B) + d[6]), 11); - B = rotintlft((B + F(C, D, A) + d[7]), 19); - - A = rotintlft((A + F(B, C, D) + d[8]), 3); - D = rotintlft((D + F(A, B, C) + d[9]), 7); - C = rotintlft((C + F(D, A, B) + d[10]), 11); - B = rotintlft((B + F(C, D, A) + d[11]), 19); - - A = rotintlft((A + F(B, C, D) + d[12]), 3); - D = rotintlft((D + F(A, B, C) + d[13]), 7); - C = rotintlft((C + F(D, A, B) + d[14]), 11); - B = rotintlft((B + F(C, D, A) + d[15]), 19); - } - - protected void round2(int[] d) { - A = rotintlft((A + G(B, C, D) + d[0] + 0x5a827999), 3); - D = rotintlft((D + G(A, B, C) + d[4] + 0x5a827999), 5); - C = rotintlft((C + G(D, A, B) + d[8] + 0x5a827999), 9); - B = rotintlft((B + G(C, D, A) + d[12] + 0x5a827999), 13); - - A = rotintlft((A + G(B, C, D) + d[1] + 0x5a827999), 3); - D = rotintlft((D + G(A, B, C) + d[5] + 0x5a827999), 5); - C = rotintlft((C + G(D, A, B) + d[9] + 0x5a827999), 9); - B = rotintlft((B + G(C, D, A) + d[13] + 0x5a827999), 13); - - A = rotintlft((A + G(B, C, D) + d[2] + 0x5a827999), 3); - D = rotintlft((D + G(A, B, C) + d[6] + 0x5a827999), 5); - C = rotintlft((C + G(D, A, B) + d[10] + 0x5a827999), 9); - B = rotintlft((B + G(C, D, A) + d[14] + 0x5a827999), 13); - - A = rotintlft((A + G(B, C, D) + d[3] + 0x5a827999), 3); - D = rotintlft((D + G(A, B, C) + d[7] + 0x5a827999), 5); - C = rotintlft((C + G(D, A, B) + d[11] + 0x5a827999), 9); - B = rotintlft((B + G(C, D, A) + d[15] + 0x5a827999), 13); - - } - - protected void round3(int[] d) { - A = rotintlft((A + H(B, C, D) + d[0] + 0x6ed9eba1), 3); - D = rotintlft((D + H(A, B, C) + d[8] + 0x6ed9eba1), 9); - C = rotintlft((C + H(D, A, B) + d[4] + 0x6ed9eba1), 11); - B = rotintlft((B + H(C, D, A) + d[12] + 0x6ed9eba1), 15); - - A = rotintlft((A + H(B, C, D) + d[2] + 0x6ed9eba1), 3); - D = rotintlft((D + H(A, B, C) + d[10] + 0x6ed9eba1), 9); - C = rotintlft((C + H(D, A, B) + d[6] + 0x6ed9eba1), 11); - B = rotintlft((B + H(C, D, A) + d[14] + 0x6ed9eba1), 15); - - A = rotintlft((A + H(B, C, D) + d[1] + 0x6ed9eba1), 3); - D = rotintlft((D + H(A, B, C) + d[9] + 0x6ed9eba1), 9); - C = rotintlft((C + H(D, A, B) + d[5] + 0x6ed9eba1), 11); - B = rotintlft((B + H(C, D, A) + d[13] + 0x6ed9eba1), 15); - - A = rotintlft((A + H(B, C, D) + d[3] + 0x6ed9eba1), 3); - D = rotintlft((D + H(A, B, C) + d[11] + 0x6ed9eba1), 9); - C = rotintlft((C + H(D, A, B) + d[7] + 0x6ed9eba1), 11); - B = rotintlft((B + H(C, D, A) + d[15] + 0x6ed9eba1), 15); - - } - - } - - /** - * Cryptography support - HMACMD5 - algorithmically based on various web - * resources by Karl Wright - */ - static class HMACMD5 { - protected byte[] ipad; - protected byte[] opad; - protected MessageDigest md5; - - HMACMD5(byte[] key) throws NTLMEngineException { - try { - md5 = MessageDigest.getInstance("MD5"); - } catch (Exception ex) { - // Umm, the algorithm doesn't exist - throw an - // NTLMEngineException! - throw new NTLMEngineException("Error getting md5 message digest implementation: " + ex.getMessage(), ex); - } - - // Initialize the pad buffers with the key - ipad = new byte[64]; - opad = new byte[64]; - - int keyLength = key.length; - if (keyLength > 64) { - // Use MD5 of the key instead, as described in RFC 2104 - md5.update(key); - key = md5.digest(); - keyLength = key.length; - } - int i = 0; - while (i < keyLength) { - ipad[i] = (byte) (key[i] ^ (byte) 0x36); - opad[i] = (byte) (key[i] ^ (byte) 0x5c); - i++; - } - while (i < 64) { - ipad[i] = (byte) 0x36; - opad[i] = (byte) 0x5c; - i++; - } - - // Very important: update the digest with the ipad buffer - md5.reset(); - md5.update(ipad); - - } - - /** - * Grab the current digest. This is the "answer". - */ - byte[] getOutput() { - byte[] digest = md5.digest(); - md5.update(opad); - return md5.digest(digest); - } - - /** - * Update by adding a complete array - */ - void update(byte[] input) { - md5.update(input); - } - - /** - * Update the algorithm - */ - void update(byte[] input, int offset, int length) { - md5.update(input, offset, length); - } - - } - - public String generateType1Msg(final String domain, final String workstation) throws NTLMEngineException { - return getType1Message(workstation, domain); - } - - public String generateType3Msg(final String username, final String password, final String domain, final String workstation, - final String challenge) throws NTLMEngineException { - Type2Message t2m = new Type2Message(challenge); - return getType3Message(username, password, workstation, domain, t2m.getChallenge(), t2m.getFlags(), t2m.getTarget(), - t2m.getTargetInfo()); - } - -} diff --git a/api/src/main/java/org/asynchttpclient/ntlm/NTLMEngineException.java b/api/src/main/java/org/asynchttpclient/ntlm/NTLMEngineException.java deleted file mode 100644 index a7fd1da9f3..0000000000 --- a/api/src/main/java/org/asynchttpclient/ntlm/NTLMEngineException.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file 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 - * - * 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. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.asynchttpclient.ntlm; - -/** - * Signals NTLM protocol failure. - * - * @since 4.0 - */ -public class NTLMEngineException extends Exception { - - private static final long serialVersionUID = 6027981323731768824L; - - public NTLMEngineException() { - super(); - } - - /** - * Creates a new NTLMEngineException with the specified message. - * - * @param message the exception detail message - */ - public NTLMEngineException(String message) { - super(message); - } - - /** - * Creates a new NTLMEngineException with the specified detail message and cause. - * - * @param message the exception detail message - * @param cause the Throwable that caused this exception, or null - * if the cause is unavailable, unknown, or not a Throwable - */ - public NTLMEngineException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/api/src/main/java/org/asynchttpclient/oauth/ConsumerKey.java b/api/src/main/java/org/asynchttpclient/oauth/ConsumerKey.java deleted file mode 100644 index 1962798330..0000000000 --- a/api/src/main/java/org/asynchttpclient/oauth/ConsumerKey.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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; - -/** - * Value class for OAuth consumer keys. - */ -public class ConsumerKey { - private final String key; - private final String secret; - - public ConsumerKey(String key, String secret) { - this.key = key; - this.secret = secret; - } - - public String getKey() { - return key; - } - - public String getSecret() { - return secret; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder("{Consumer key, key="); - appendValue(sb, key); - sb.append(", secret="); - appendValue(sb, secret); - sb.append("}"); - return sb.toString(); - } - - private void appendValue(StringBuilder sb, String value) { - if (value == null) { - sb.append("null"); - } else { - sb.append('"'); - sb.append(value); - sb.append('"'); - } - } - - @Override - public int hashCode() { - return key.hashCode() + secret.hashCode(); - } - - @Override - public boolean equals(Object o) { - if (o == this) - return true; - if (o == null || o.getClass() != getClass()) - return false; - ConsumerKey other = (ConsumerKey) o; - return key.equals(other.key) && secret.equals(other.secret); - } -} diff --git a/api/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java b/api/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java deleted file mode 100644 index 6d376fa733..0000000000 --- a/api/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.Param; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilderBase; -import org.asynchttpclient.SignatureCalculator; -import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.Base64; -import org.asynchttpclient.util.StandardCharsets; -import org.asynchttpclient.util.UTF8UrlEncoder; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; - -/** - * Simple OAuth signature calculator that can used for constructing client signatures - * for accessing services that use OAuth for authorization. - *

- * 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. - * - * @author tatu (tatu.saloranta@iki.fi) - */ -public class OAuthSignatureCalculator implements SignatureCalculator { - public final static String HEADER_AUTHORIZATION = "Authorization"; - - 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"; - - /** - * To generate Nonce, need some (pseudo)randomness; no need for - * secure variant here. - */ - protected final Random random; - - protected final byte[] nonceBuffer = new byte[16]; - - protected final ThreadSafeHMAC mac; - - protected final ConsumerKey consumerAuth; - - protected 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) { - mac = new ThreadSafeHMAC(consumerAuth, userAuth); - this.consumerAuth = consumerAuth; - this.userAuth = userAuth; - random = new Random(System.identityHashCode(this) + System.currentTimeMillis()); - } - - //@Override // silly 1.5; doesn't allow this for interfaces - - public void calculateAndAddSignature(Request request, RequestBuilderBase requestBuilder) { - String nonce = generateNonce(); - long timestamp = System.currentTimeMillis() / 1000L; - String signature = calculateSignature(request.getMethod(), request.getURI(), timestamp, nonce, request.getFormParams(), request.getQueryParams()); - String headerValue = constructAuthHeader(signature, nonce, timestamp); - requestBuilder.setHeader(HEADER_AUTHORIZATION, headerValue); - } - - /** - * Method for calculating OAuth signature using HMAC/SHA-1 method. - */ - public String calculateSignature(String method, UriComponents uri, long oauthTimestamp, String nonce, - List formParams, List queryParams) { - StringBuilder signedText = new StringBuilder(100); - signedText.append(method); // POST / GET etc (nothing to URL encode) - signedText.append('&'); - - /* 07-Oct-2010, tatu: URL may contain default port number; if so, need to extract - * from base URL. - */ - String scheme = uri.getScheme(); - int port = uri.getPort(); - if (scheme.equals("http")) - if (port == 80) - port = -1; - else if (scheme.equals("https")) - if (port == 443) - port = -1; - - StringBuilder sb = new StringBuilder().append(scheme).append("://").append(uri.getHost()); - if (port != -1) - sb.append(':').append(port); - if (isNonEmpty(uri.getPath())) - sb.append(uri.getPath()); - - String baseURL = sb.toString(); - UTF8UrlEncoder.appendEncoded(signedText, baseURL); - - /** - * List of all query and form parameters added to this request; needed - * for calculating request signature - */ - OAuthParameterSet allParameters = new OAuthParameterSet(); - - // start with standard OAuth parameters we need - allParameters.add(KEY_OAUTH_CONSUMER_KEY, consumerAuth.getKey()); - allParameters.add(KEY_OAUTH_NONCE, nonce); - allParameters.add(KEY_OAUTH_SIGNATURE_METHOD, OAUTH_SIGNATURE_METHOD); - allParameters.add(KEY_OAUTH_TIMESTAMP, String.valueOf(oauthTimestamp)); - if (userAuth.getKey() != null) { - allParameters.add(KEY_OAUTH_TOKEN, userAuth.getKey()); - } - allParameters.add(KEY_OAUTH_VERSION, OAUTH_VERSION_1_0); - - if (formParams != null) { - for (Param param : formParams) { - allParameters.add(param.getName(), param.getValue()); - } - } - if (queryParams != null) { - for (Param param : queryParams) { - allParameters.add(param.getName(), param.getValue()); - } - } - String encodedParams = allParameters.sortAndConcat(); - - // and all that needs to be URL encoded (... again!) - signedText.append('&'); - UTF8UrlEncoder.appendEncoded(signedText, encodedParams); - - byte[] rawBase = signedText.toString().getBytes(StandardCharsets.UTF_8); - byte[] rawSignature = mac.digest(rawBase); - // and finally, base64 encoded... phew! - return Base64.encode(rawSignature); - } - - /** - * Method used for constructing - */ - public String constructAuthHeader(String signature, String nonce, long oauthTimestamp) { - StringBuilder sb = new StringBuilder(200); - sb.append("OAuth "); - sb.append(KEY_OAUTH_CONSUMER_KEY).append("=\"").append(consumerAuth.getKey()).append("\", "); - if (userAuth.getKey() != null) { - sb.append(KEY_OAUTH_TOKEN).append("=\"").append(userAuth.getKey()).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.appendEncoded(sb, signature).append("\", "); - sb.append(KEY_OAUTH_TIMESTAMP).append("=\"").append(oauthTimestamp).append("\", "); - - // also: nonce may contain things that need URL encoding (esp. when using base64): - sb.append(KEY_OAUTH_NONCE).append("=\""); - UTF8UrlEncoder.appendEncoded(sb, nonce); - sb.append("\", "); - - sb.append(KEY_OAUTH_VERSION).append("=\"").append(OAUTH_VERSION_1_0).append("\""); - return sb.toString(); - } - - private synchronized String generateNonce() { - random.nextBytes(nonceBuffer); - // let's use base64 encoding over hex, slightly more compact than hex or decimals - return Base64.encode(nonceBuffer); -// return String.valueOf(Math.abs(random.nextLong())); - } - - /** - * Container for parameters used for calculating OAuth signature. - * About the only confusing aspect is that of whether entries are to be sorted - * before encoded or vice versa: if my reading is correct, encoding is to occur - * first, then sorting; although this should rarely matter (since sorting is primary - * by key, which usually has nothing to encode)... of course, rarely means that - * when it would occur it'd be harder to track down. - */ - final static class OAuthParameterSet { - final private ArrayList allParameters = new ArrayList(); - - public OAuthParameterSet() { - } - - public OAuthParameterSet add(String key, String value) { - Parameter p = new Parameter(UTF8UrlEncoder.encode(key), UTF8UrlEncoder.encode(value)); - allParameters.add(p); - return this; - } - - public String sortAndConcat() { - // then sort them (AFTER encoding, important) - Parameter[] params = allParameters.toArray(new Parameter[allParameters.size()]); - Arrays.sort(params); - - // and build parameter section using pre-encoded pieces: - StringBuilder encodedParams = new StringBuilder(100); - for (Parameter param : params) { - if (encodedParams.length() > 0) { - encodedParams.append('&'); - } - encodedParams.append(param.key()).append('=').append(param.value()); - } - return encodedParams.toString(); - } - } - - /** - * Helper class for sorting query and form parameters that we need - */ - final static class Parameter implements Comparable { - private final String key, value; - - public Parameter(String key, String value) { - this.key = key; - this.value = value; - } - - public String key() { - return key; - } - - public String value() { - return value; - } - - //@Override // silly 1.5; doesn't allow this for interfaces - - public int compareTo(Parameter other) { - int diff = key.compareTo(other.key); - if (diff == 0) { - diff = value.compareTo(other.value); - } - return diff; - } - - @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; - - if (!key.equals(parameter.key)) return false; - if (!value.equals(parameter.value)) return false; - - return true; - } - - @Override - public int hashCode() { - int result = key.hashCode(); - result = 31 * result + value.hashCode(); - return result; - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/oauth/RequestToken.java b/api/src/main/java/org/asynchttpclient/oauth/RequestToken.java deleted file mode 100644 index 2fd02c4244..0000000000 --- a/api/src/main/java/org/asynchttpclient/oauth/RequestToken.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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; - -/** - * 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; - - public RequestToken(String key, String token) { - this.key = key; - this.secret = token; - } - - public String getKey() { - return key; - } - - public String getSecret() { - return secret; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder("{ key="); - appendValue(sb, key); - sb.append(", secret="); - appendValue(sb, secret); - sb.append("}"); - return sb.toString(); - } - - private void appendValue(StringBuilder sb, String value) { - if (value == null) { - sb.append("null"); - } else { - sb.append('"'); - sb.append(value); - sb.append('"'); - } - } - - @Override - public int hashCode() { - return key.hashCode() + secret.hashCode(); - } - - @Override - public boolean equals(Object o) { - if (o == this) - return true; - if (o == null || o.getClass() != getClass()) - return false; - RequestToken other = (RequestToken) o; - return key.equals(other.key) && secret.equals(other.secret); - } -} diff --git a/api/src/main/java/org/asynchttpclient/oauth/ThreadSafeHMAC.java b/api/src/main/java/org/asynchttpclient/oauth/ThreadSafeHMAC.java deleted file mode 100644 index 028020f758..0000000000 --- a/api/src/main/java/org/asynchttpclient/oauth/ThreadSafeHMAC.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.StandardCharsets; -import org.asynchttpclient.util.UTF8UrlEncoder; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -/** - * Since cloning (of MAC instances) is not necessarily supported on all platforms - * (and specifically seems to fail on MacOS), let's wrap synchronization/reuse details here. - * Assumption is that this is bit more efficient (even considering synchronization) - * than locating and reconstructing instance each time. - * In future we may want to use soft references and thread local instance. - * - * @author tatu (tatu.saloranta@iki.fi) - */ -public class ThreadSafeHMAC { - private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; - - private final Mac mac; - - public ThreadSafeHMAC(ConsumerKey consumerAuth, RequestToken userAuth) { - byte[] keyBytes = (UTF8UrlEncoder.encode(consumerAuth.getSecret()) + "&" + UTF8UrlEncoder.encode(userAuth.getSecret())) - .getBytes(StandardCharsets.UTF_8); - SecretKeySpec signingKey = new SecretKeySpec(keyBytes, HMAC_SHA1_ALGORITHM); - - // Get an hmac_sha1 instance and initialize with the signing key - try { - mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); - mac.init(signingKey); - } catch (Exception e) { - throw new IllegalArgumentException(e); - } - - } - - public synchronized byte[] digest(byte[] message) { - mac.reset(); - return mac.doFinal(message); - } -} diff --git a/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java b/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java deleted file mode 100644 index 202a8bc494..0000000000 --- a/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.asynchttpclient.providers; - -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.AsyncHttpProviderUtils; - -import java.util.Collections; -import java.util.List; - -public abstract class ResponseBase implements Response { - protected final static String DEFAULT_CHARSET = "ISO-8859-1"; - - protected final List bodyParts; - protected final HttpResponseHeaders headers; - protected final HttpResponseStatus status; - private List cookies; - - protected ResponseBase(HttpResponseStatus status, HttpResponseHeaders headers, List bodyParts) { - this.bodyParts = bodyParts; - this.headers = headers; - this.status = status; - } - - protected abstract List buildCookies(); - - protected String calculateCharset(String charset) { - - if (charset == null) { - String contentType = getContentType(); - if (contentType != null) - charset = AsyncHttpProviderUtils.parseCharset(contentType); // parseCharset can return null - } - return charset != null ? charset : DEFAULT_CHARSET; - } - - @Override - public final int getStatusCode() { - return status.getStatusCode(); - } - - @Override - public final String getStatusText() { - return status.getStatusText(); - } - - @Override - public final UriComponents getUri() { - return status.getUri(); - } - - @Override - public final String getContentType() { - return headers != null ? getHeader("Content-Type") : null; - } - - @Override - public final String getHeader(String name) { - return headers != null ? getHeaders().getFirstValue(name) : null; - } - - @Override - public final List getHeaders(String name) { - return headers != null ? getHeaders().get(name) : null; - } - - @Override - public final FluentCaseInsensitiveStringsMap getHeaders() { - return headers != null ? headers.getHeaders() : new FluentCaseInsensitiveStringsMap(); - } - - @Override - public final boolean isRedirected() { - switch (status.getStatusCode()) { - case 301: - case 302: - case 303: - case 307: - case 308: - return true; - default: - return false; - } - } - - @Override - public List getCookies() { - - if (headers == null) { - return Collections.emptyList(); - } - - if (cookies == null) { - cookies = buildCookies(); - } - return cookies; - - } - - @Override - public boolean hasResponseStatus() { - return status != null; - } - - @Override - public boolean hasResponseHeaders() { - return headers != null && isNonEmpty(headers.getHeaders()); - } - - @Override - public boolean hasResponseBody() { - return isNonEmpty(bodyParts); - } -} diff --git a/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java b/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java deleted file mode 100644 index 030a95fb7f..0000000000 --- a/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java +++ /dev/null @@ -1,123 +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.resumable; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.util.Map; -import java.util.Scanner; -import java.util.concurrent.ConcurrentHashMap; - -import org.asynchttpclient.util.StandardCharsets; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A {@link org.asynchttpclient.resumable.ResumableAsyncHandler.ResumableProcessor} which use a properties file - * to store the download index information. - */ -public class PropertiesBasedResumableProcessor implements ResumableAsyncHandler.ResumableProcessor { - private final static Logger log = LoggerFactory.getLogger(PropertiesBasedResumableProcessor.class); - private final static File TMP = new File(System.getProperty("java.io.tmpdir"), "ahc"); - private final static String storeName = "ResumableAsyncHandler.properties"; - private final ConcurrentHashMap properties = new ConcurrentHashMap(); - - /** - * {@inheritDoc} - */ - @Override - public void put(String url, long transferredBytes) { - properties.put(url, transferredBytes); - } - - /** - * {@inheritDoc} - */ - @Override - public void remove(String uri) { - if (uri != null) { - properties.remove(uri); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void save(Map map) { - log.debug("Saving current download state {}", properties.toString()); - FileOutputStream os = null; - try { - - if (!TMP.exists() && !TMP.mkdirs()) { - throw new IllegalStateException("Unable to create directory: " + TMP.getAbsolutePath()); - } - File f = new File(TMP, storeName); - if (!f.exists() && !f.createNewFile()) { - throw new IllegalStateException("Unable to create temp file: " + f.getAbsolutePath()); - } - if (!f.canWrite()) { - throw new IllegalStateException(); - } - - os = new FileOutputStream(f); - - for (Map.Entry e : properties.entrySet()) { - os.write(append(e).getBytes(StandardCharsets.UTF_8)); - } - os.flush(); - } catch (Throwable e) { - log.warn(e.getMessage(), e); - } finally { - if (os != null) - closeSilently(os); - } - } - - private static String append(Map.Entry e) { - return new StringBuilder(e.getKey()).append("=").append(e.getValue()).append("\n").toString(); - } - - /** - * {@inheritDoc} - */ - @Override - public Map load() { - Scanner scan = null; - try { - scan = new Scanner(new File(TMP, storeName), StandardCharsets.UTF_8.name()); - scan.useDelimiter("[=\n]"); - - String key; - String value; - while (scan.hasNext()) { - key = scan.next().trim(); - value = scan.next().trim(); - properties.put(key, Long.valueOf(value)); - } - log.debug("Loading previous download state {}", properties.toString()); - } catch (FileNotFoundException ex) { - log.debug("Missing {}", storeName); - } catch (Throwable ex) { - // Survive any exceptions - log.warn(ex.getMessage(), ex); - } finally { - if (scan != null) - scan.close(); - } - return properties; - } -} diff --git a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java b/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java deleted file mode 100644 index f91cdeb313..0000000000 --- a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java +++ /dev/null @@ -1,316 +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.resumable; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.Response.ResponseBuilder; -import org.asynchttpclient.listener.TransferCompletionHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicLong; - -/** - * 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 - * download the entire file again. It's the responsibility of the {@link org.asynchttpclient.resumable.ResumableAsyncHandler} - * to track how many bytes has been transferred and to properly adjust the file's write position. - *

- * In case of a JVM crash/shutdown, you can create an instance of this class and pass the last valid bytes position. - */ -public class ResumableAsyncHandler implements AsyncHandler { - private final static Logger logger = LoggerFactory.getLogger(TransferCompletionHandler.class); - private final AtomicLong byteTransferred; - private String url; - private final ResumableProcessor resumableProcessor; - private final AsyncHandler decoratedAsyncHandler; - private static Map resumableIndex; - private final static ResumableIndexThread resumeIndexThread = new ResumableIndexThread(); - private ResponseBuilder responseBuilder = new ResponseBuilder(); - private final boolean accumulateBody; - private ResumableListener resumableListener = new NULLResumableListener(); - - private ResumableAsyncHandler(long byteTransferred, ResumableProcessor resumableProcessor, - AsyncHandler decoratedAsyncHandler, boolean accumulateBody) { - - this.byteTransferred = new AtomicLong(byteTransferred); - - if (resumableProcessor == null) { - resumableProcessor = new NULLResumableHandler(); - } - this.resumableProcessor = resumableProcessor; - - resumableIndex = resumableProcessor.load(); - resumeIndexThread.addResumableProcessor(resumableProcessor); - - this.decoratedAsyncHandler = decoratedAsyncHandler; - this.accumulateBody = accumulateBody; - } - - public ResumableAsyncHandler(long byteTransferred) { - this(byteTransferred, null, null, false); - } - - public ResumableAsyncHandler(boolean accumulateBody) { - this(0, null, null, accumulateBody); - } - - public ResumableAsyncHandler() { - this(0, null, null, false); - } - - public ResumableAsyncHandler(AsyncHandler decoratedAsyncHandler) { - this(0, new PropertiesBasedResumableProcessor(), decoratedAsyncHandler, false); - } - - public ResumableAsyncHandler(long byteTransferred, AsyncHandler decoratedAsyncHandler) { - this(byteTransferred, new PropertiesBasedResumableProcessor(), decoratedAsyncHandler, false); - } - - public ResumableAsyncHandler(ResumableProcessor resumableProcessor) { - this(0, resumableProcessor, null, false); - } - - public ResumableAsyncHandler(ResumableProcessor resumableProcessor, boolean accumulateBody) { - this(0, resumableProcessor, null, accumulateBody); - } - - /** - * {@inheritDoc} - */ - @Override - public AsyncHandler.STATE onStatusReceived(final HttpResponseStatus status) throws Exception { - responseBuilder.accumulate(status); - if (status.getStatusCode() == 200 || status.getStatusCode() == 206) { - url = status.getUri().toUrl(); - } else { - return AsyncHandler.STATE.ABORT; - } - - if (decoratedAsyncHandler != null) { - return decoratedAsyncHandler.onStatusReceived(status); - } - - return AsyncHandler.STATE.CONTINUE; - } - - /** - * {@inheritDoc} - */ - @Override - public void onThrowable(Throwable t) { - if (decoratedAsyncHandler != null) { - decoratedAsyncHandler.onThrowable(t); - } else { - logger.debug("", t); - } - } - - /** - * {@inheritDoc} - */ - @Override - public AsyncHandler.STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - - if (accumulateBody) { - responseBuilder.accumulate(bodyPart); - } - - STATE state = STATE.CONTINUE; - try { - resumableListener.onBytesReceived(bodyPart.getBodyByteBuffer()); - } catch (IOException ex) { - return AsyncHandler.STATE.ABORT; - } - - if (decoratedAsyncHandler != null) { - state = decoratedAsyncHandler.onBodyPartReceived(bodyPart); - } - - byteTransferred.addAndGet(bodyPart.getBodyPartBytes().length); - resumableProcessor.put(url, byteTransferred.get()); - - return state; - } - - /** - * {@inheritDoc} - */ - @Override - public Response onCompleted() throws Exception { - resumableProcessor.remove(url); - resumableListener.onAllBytesReceived(); - - if (decoratedAsyncHandler != null) { - decoratedAsyncHandler.onCompleted(); - } - // Not sure - return responseBuilder.build(); - } - - /** - * {@inheritDoc} - */ - @Override - public AsyncHandler.STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { - responseBuilder.accumulate(headers); - String contentLengthHeader = headers.getHeaders().getFirstValue("Content-Length"); - if (contentLengthHeader != null) { - if (Long.parseLong(contentLengthHeader) == -1L) { - return AsyncHandler.STATE.ABORT; - } - } - - if (decoratedAsyncHandler != null) { - return decoratedAsyncHandler.onHeadersReceived(headers); - } - return AsyncHandler.STATE.CONTINUE; - } - - /** - * Invoke this API if you want to set the Range header on your {@link Request} based on the last valid bytes - * position. - * - * @param request {@link Request} - * @return a {@link Request} with the Range header properly set. - */ - public Request adjustRequestRange(Request request) { - - Long ri = resumableIndex.get(request.getURI().toUrl()); - if (ri != null) { - byteTransferred.set(ri); - } - - // The Resumbale - if (resumableListener != null && resumableListener.length() > 0 && byteTransferred.get() != resumableListener.length()) { - byteTransferred.set(resumableListener.length()); - } - - RequestBuilder builder = new RequestBuilder(request); - if (request.getHeaders().get("Range") == null && byteTransferred.get() != 0) { - builder.setHeader("Range", "bytes=" + byteTransferred.get() + "-"); - } - return builder.build(); - } - - /** - * Set a {@link ResumableListener} - * - * @param resumableListener a {@link ResumableListener} - * @return this - */ - public ResumableAsyncHandler setResumableListener(ResumableListener resumableListener) { - this.resumableListener = resumableListener; - return this; - } - - private static class ResumableIndexThread extends Thread { - - public final ConcurrentLinkedQueue resumableProcessors = new ConcurrentLinkedQueue(); - - public ResumableIndexThread() { - Runtime.getRuntime().addShutdownHook(this); - } - - public void addResumableProcessor(ResumableProcessor p) { - resumableProcessors.offer(p); - } - - public void run() { - for (ResumableProcessor p : resumableProcessors) { - p.save(resumableIndex); - } - } - } - - /** - * An interface to implement in order to manage the way the incomplete file management are handled. - */ - public static interface ResumableProcessor { - - /** - * Associate a key with the number of bytes successfully transferred. - * - * @param key a key. The recommended way is to use an url. - * @param transferredBytes The number of bytes successfully transferred. - */ - void put(String key, long transferredBytes); - - /** - * Remove the key associate value. - * - * @param key key from which the value will be discarded - */ - void remove(String key); - - /** - * Save the current {@link Map} instance which contains information about the current transfer state. - * This method *only* invoked when the JVM is shutting down. - * - * @param map - */ - void save(Map map); - - /** - * Load the {@link Map} in memory, contains information about the transferred bytes. - * - * @return {@link Map} - */ - Map load(); - - } - - private static class NULLResumableHandler implements ResumableProcessor { - - public void put(String url, long transferredBytes) { - } - - public void remove(String uri) { - } - - public void save(Map map) { - } - - public Map load() { - return new HashMap(); - } - } - - private static class NULLResumableListener implements ResumableListener { - - private long length = 0L; - - public void onBytesReceived(ByteBuffer byteBuffer) throws IOException { - length += byteBuffer.remaining(); - } - - public void onAllBytesReceived() { - } - - public long length() { - return length; - } - - } -} diff --git a/api/src/main/java/org/asynchttpclient/resumable/ResumableIOExceptionFilter.java b/api/src/main/java/org/asynchttpclient/resumable/ResumableIOExceptionFilter.java deleted file mode 100644 index 87868e35de..0000000000 --- a/api/src/main/java/org/asynchttpclient/resumable/ResumableIOExceptionFilter.java +++ /dev/null @@ -1,33 +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.resumable; - -import org.asynchttpclient.Request; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.IOExceptionFilter; - -/** - * Simple {@link org.asynchttpclient.filter.IOExceptionFilter} that replay the current {@link org.asynchttpclient.Request} using a {@link ResumableAsyncHandler} - */ -public class ResumableIOExceptionFilter implements IOExceptionFilter { - public FilterContext filter(FilterContext ctx) throws FilterException { - if (ctx.getIOException() != null && ctx.getAsyncHandler() instanceof ResumableAsyncHandler) { - - Request request = ResumableAsyncHandler.class.cast(ctx.getAsyncHandler()).adjustRequestRange(ctx.getRequest()); - - return new FilterContext.FilterContextBuilder(ctx).request(request).replayRequest(true).build(); - } - return ctx; - } -} diff --git a/api/src/main/java/org/asynchttpclient/simple/HeaderMap.java b/api/src/main/java/org/asynchttpclient/simple/HeaderMap.java deleted file mode 100644 index 92476cc6a3..0000000000 --- a/api/src/main/java/org/asynchttpclient/simple/HeaderMap.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.asynchttpclient.simple; - -/* - * Copyright (c) 2010 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. - */ - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * A map containing headers with the sole purpose of being given to - * {@link SimpleAHCTransferListener#onHeaders(String, HeaderMap)}. - * - * @author Benjamin Hanzelmann - */ -public class HeaderMap implements Map> { - - private FluentCaseInsensitiveStringsMap headers; - - public HeaderMap(FluentCaseInsensitiveStringsMap headers) { - this.headers = headers; - } - - public Set keySet() { - return headers.keySet(); - } - - public Set>> entrySet() { - return headers.entrySet(); - } - - public int size() { - return headers.size(); - } - - public boolean isEmpty() { - return headers.isEmpty(); - } - - public boolean containsKey(Object key) { - return headers.containsKey(key); - } - - public boolean containsValue(Object value) { - return headers.containsValue(value); - } - - /** - * @see FluentCaseInsensitiveStringsMap#getFirstValue(String) - */ - public String getFirstValue(String key) { - return headers.getFirstValue(key); - } - - /** - * @see FluentCaseInsensitiveStringsMap#getJoinedValue(String, String) - */ - public String getJoinedValue(String key, String delimiter) { - return headers.getJoinedValue(key, delimiter); - } - - public List get(Object key) { - return headers.get(key); - } - - /** - * Only read access is supported. - */ - public List put(String key, List value) { - throw new UnsupportedOperationException("Only read access is supported."); - } - - /** - * Only read access is supported. - */ - public List remove(Object key) { - throw new UnsupportedOperationException("Only read access is supported."); - } - - /** - * Only read access is supported. - */ - public void putAll(Map> t) { - throw new UnsupportedOperationException("Only read access is supported."); - - } - - /** - * Only read access is supported. - */ - public void clear() { - throw new UnsupportedOperationException("Only read access is supported."); - } - - /** - * Only read access is supported. - */ - public Collection> values() { - return headers.values(); - } - -} diff --git a/api/src/main/java/org/asynchttpclient/simple/SimpleAHCTransferListener.java b/api/src/main/java/org/asynchttpclient/simple/SimpleAHCTransferListener.java deleted file mode 100644 index 02702a0421..0000000000 --- a/api/src/main/java/org/asynchttpclient/simple/SimpleAHCTransferListener.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.asynchttpclient.simple; - -/* - * 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. - */ - -import org.asynchttpclient.SimpleAsyncHttpClient; -import org.asynchttpclient.uri.UriComponents; - -/** - * A simple transfer listener for use with the {@link SimpleAsyncHttpClient}. - *

- * Note: This listener does not cover requests failing before a connection is - * established. For error handling, see - * {@link org.asynchttpclient.SimpleAsyncHttpClient.Builder#setDefaultThrowableHandler(org.asynchttpclient.ThrowableHandler)} - * - * @author Benjamin Hanzelmann - */ -public interface SimpleAHCTransferListener { - - /** - * This method is called after the connection status is received. - * - * @param uri the uri - * @param statusCode the received status code. - * @param statusText the received status text. - */ - void onStatus(UriComponents uri, int statusCode, String statusText); - - /** - * This method is called after the response headers are received. - * - * @param uri the uri - * @param headers the received headers, never {@code null}. - */ - void onHeaders(UriComponents uri, HeaderMap headers); - - /** - * This method is called when bytes of the responses body are received. - * - * @param uri the uri - * @param amount the number of transferred bytes so far. - * @param current the number of transferred bytes since the last call to this - * method. - * @param total the total number of bytes to be transferred. This is taken - * from the Content-Length-header and may be unspecified (-1). - */ - void onBytesReceived(UriComponents uri, long amount, long current, long total); - - /** - * This method is called when bytes are sent. - * - * @param uri the uri - * @param amount the number of transferred bytes so far. - * @param current the number of transferred bytes since the last call to this - * method. - * @param total the total number of bytes to be transferred. This is taken - * from the Content-Length-header and may be unspecified (-1). - */ - void onBytesSent(UriComponents uri, long amount, long current, long total); - - /** - * This method is called when the request is completed. - * - * @param uri the uri - * @param statusCode the received status code. - * @param statusText the received status text. - */ - void onCompleted(UriComponents uri, int statusCode, String statusText); -} - diff --git a/api/src/main/java/org/asynchttpclient/spnego/SpnegoEngine.java b/api/src/main/java/org/asynchttpclient/spnego/SpnegoEngine.java deleted file mode 100644 index dc371e0294..0000000000 --- a/api/src/main/java/org/asynchttpclient/spnego/SpnegoEngine.java +++ /dev/null @@ -1,174 +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. - */ -/* - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file 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 - * - * 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. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - */ - -package org.asynchttpclient.spnego; - -import org.asynchttpclient.util.Base64; -import org.ietf.jgss.GSSContext; -import org.ietf.jgss.GSSException; -import org.ietf.jgss.GSSManager; -import org.ietf.jgss.GSSName; -import org.ietf.jgss.Oid; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -/** - * SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication scheme. - * - * @since 4.1 - */ -public class SpnegoEngine { - - private static final String SPNEGO_OID = "1.3.6.1.5.5.2"; - private static final String KERBEROS_OID = "1.2.840.113554.1.2.2"; - - private final Logger log = LoggerFactory.getLogger(getClass()); - - private final SpnegoTokenGenerator spnegoGenerator; - - public SpnegoEngine(final SpnegoTokenGenerator spnegoGenerator) { - this.spnegoGenerator = spnegoGenerator; - } - - public SpnegoEngine() { - this(null); - } - - private static SpnegoEngine instance; - - public static SpnegoEngine instance() { - if (instance == null) - instance = new SpnegoEngine(); - return instance; - } - - public String generateToken(String server) throws Throwable { - GSSContext gssContext = null; - byte[] token = null; // base64 decoded challenge - Oid negotiationOid = null; - - try { - log.debug("init {}", server); - /* - * Using the SPNEGO OID is the correct method. Kerberos v5 works for IIS but not JBoss. Unwrapping the initial token when using SPNEGO OID looks like what is described - * here... - * - * http://msdn.microsoft.com/en-us/library/ms995330.aspx - * - * Another helpful URL... - * - * http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/tsec_SPNEGO_token.html - * - * Unfortunately SPNEGO is JRE >=1.6. - */ - - /** Try SPNEGO by default, fall back to Kerberos later if error */ - negotiationOid = new Oid(SPNEGO_OID); - - boolean tryKerberos = false; - try { - GSSManager manager = GSSManager.getInstance(); - GSSName serverName = manager.createName("HTTP@" + server, GSSName.NT_HOSTBASED_SERVICE); - gssContext = manager.createContext(serverName.canonicalize(negotiationOid), negotiationOid, null, - GSSContext.DEFAULT_LIFETIME); - gssContext.requestMutualAuth(true); - gssContext.requestCredDeleg(true); - } catch (GSSException ex) { - log.error("generateToken", ex); - // BAD MECH means we are likely to be using 1.5, fall back to Kerberos MECH. - // Rethrow any other exception. - if (ex.getMajor() == GSSException.BAD_MECH) { - log.debug("GSSException BAD_MECH, retry with Kerberos MECH"); - tryKerberos = true; - } else { - throw ex; - } - - } - if (tryKerberos) { - /* Kerberos v5 GSS-API mechanism defined in RFC 1964. */ - log.debug("Using Kerberos MECH {}", KERBEROS_OID); - negotiationOid = new Oid(KERBEROS_OID); - GSSManager manager = GSSManager.getInstance(); - GSSName serverName = manager.createName("HTTP@" + server, GSSName.NT_HOSTBASED_SERVICE); - gssContext = manager.createContext(serverName.canonicalize(negotiationOid), negotiationOid, null, - GSSContext.DEFAULT_LIFETIME); - gssContext.requestMutualAuth(true); - gssContext.requestCredDeleg(true); - } - - // TODO suspicious: this will always be null because no value has been assigned before. Assign directly? - if (token == null) { - token = new byte[0]; - } - - token = gssContext.initSecContext(token, 0, token.length); - if (token == null) { - throw new Exception("GSS security context initialization failed"); - } - - /* - * IIS accepts Kerberos and SPNEGO tokens. Some other servers Jboss, Glassfish? seem to only accept SPNEGO. Below wraps Kerberos into SPNEGO token. - */ - if (spnegoGenerator != null && negotiationOid.toString().equals(KERBEROS_OID)) { - token = spnegoGenerator.generateSpnegoDERObject(token); - } - - gssContext.dispose(); - - String tokenstr = new String(Base64.encode(token)); - log.debug("Sending response '{}' back to the server", tokenstr); - - return tokenstr; - } catch (GSSException gsse) { - log.error("generateToken", gsse); - if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) - throw new Exception(gsse.getMessage(), gsse); - if (gsse.getMajor() == GSSException.NO_CRED) - throw new Exception(gsse.getMessage(), gsse); - if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN || gsse.getMajor() == GSSException.DUPLICATE_TOKEN - || gsse.getMajor() == GSSException.OLD_TOKEN) - throw new Exception(gsse.getMessage(), gsse); - // other error - throw new Exception(gsse.getMessage()); - } catch (IOException ex) { - throw new Exception(ex.getMessage()); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/uri/UriComponents.java b/api/src/main/java/org/asynchttpclient/uri/UriComponents.java deleted file mode 100644 index 4fc255fc6b..0000000000 --- a/api/src/main/java/org/asynchttpclient/uri/UriComponents.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.uri; - -import java.net.URI; -import java.net.URISyntaxException; - -import org.asynchttpclient.util.MiscUtils; - -public class UriComponents { - - public static UriComponents create(String originalUrl) { - return create(null, originalUrl); - } - - public static UriComponents create(UriComponents context, final String originalUrl) { - UriComponentsParser parser = new UriComponentsParser(); - parser.parse(context, originalUrl); - - return new UriComponents(parser.scheme,// - parser.userInfo,// - parser.host,// - parser.port,// - parser.path,// - parser.query); - } - - private final String scheme; - private final String userInfo; - private final String host; - private final int port; - private final String query; - private final String path; - - public UriComponents(String scheme,// - String userInfo,// - String host,// - int port,// - String path,// - String query) { - - if (scheme == null) - throw new NullPointerException("scheme"); - if (host == null) - throw new NullPointerException("host"); - - this.scheme = scheme; - this.userInfo = userInfo; - this.host = host; - this.port = port; - this.path = path; - this.query = query; - } - - public String getQuery() { - return query; - } - - public String getPath() { - return path; - } - - public String getUserInfo() { - return userInfo; - } - - public int getPort() { - return port; - } - - public String getScheme() { - return scheme; - } - - public String getHost() { - return host; - } - - public URI toURI() throws URISyntaxException { - return new URI(toUrl()); - } - - public String toUrl() { - StringBuilder sb = new StringBuilder(); - sb.append(scheme).append("://"); - if (userInfo != null) - sb.append(userInfo).append('@'); - sb.append(host); - if (port != -1) - sb.append(':').append(port); - if (path != null) - sb.append(path); - if (query != null) - sb.append('?').append(query); - - return sb.toString(); - } - - public String toRelativeUrl() { - StringBuilder sb = new StringBuilder(); - if (MiscUtils.isNonEmpty(path)) - sb.append(path); - else - sb.append('/'); - if (query != null) - sb.append('?').append(query); - - return sb.toString(); - } - - @Override - public String toString() { - // for now, but might change - return toUrl(); - } - - public UriComponents withNewScheme(String newScheme) { - return new UriComponents(newScheme,// - userInfo,// - host,// - port,// - path,// - query); - } - - public UriComponents withNewQuery(String newQuery) { - return new UriComponents(scheme,// - userInfo,// - host,// - port,// - path,// - newQuery); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((host == null) ? 0 : host.hashCode()); - result = prime * result + ((path == null) ? 0 : path.hashCode()); - result = prime * result + port; - result = prime * result + ((query == null) ? 0 : query.hashCode()); - result = prime * result + ((scheme == null) ? 0 : scheme.hashCode()); - result = prime * result + ((userInfo == null) ? 0 : userInfo.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - UriComponents other = (UriComponents) obj; - if (host == null) { - if (other.host != null) - return false; - } else if (!host.equals(other.host)) - return false; - if (path == null) { - if (other.path != null) - return false; - } else if (!path.equals(other.path)) - return false; - if (port != other.port) - return false; - if (query == null) { - if (other.query != null) - return false; - } else if (!query.equals(other.query)) - return false; - if (scheme == null) { - if (other.scheme != null) - return false; - } else if (!scheme.equals(other.scheme)) - return false; - if (userInfo == null) { - if (other.userInfo != null) - return false; - } else if (!userInfo.equals(other.userInfo)) - return false; - return true; - } -} diff --git a/api/src/main/java/org/asynchttpclient/uri/UriComponentsParser.java b/api/src/main/java/org/asynchttpclient/uri/UriComponentsParser.java deleted file mode 100644 index a8dfc0796d..0000000000 --- a/api/src/main/java/org/asynchttpclient/uri/UriComponentsParser.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.uri; - -final class UriComponentsParser { - - public String scheme; - public String host; - public int port = -1; - public String query; - public String authority; - public String path; - public String userInfo; - - private int start, end = 0; - private String urlWithoutQuery; - - private void trimRight(String originalUrl) { - end = originalUrl.length(); - while (end > 0 && originalUrl.charAt(end - 1) <= ' ') - end--; - } - - private void trimLeft(String originalUrl) { - while (start < end && originalUrl.charAt(start) <= ' ') - start++; - - if (originalUrl.regionMatches(true, start, "url:", 0, 4)) - start += 4; - } - - private boolean isFragmentOnly(String originalUrl) { - return start < originalUrl.length() && originalUrl.charAt(start) == '#'; - } - - private boolean isValidProtocolChar(char c) { - return Character.isLetterOrDigit(c) && c != '.' && c != '+' && c != '-'; - } - - private boolean isValidProtocolChars(String protocol) { - for (int i = 1; i < protocol.length(); i++) { - if (!isValidProtocolChar(protocol.charAt(i))) - return false; - } - return true; - } - - private boolean isValidProtocol(String protocol) { - return protocol.length() > 0 && Character.isLetter(protocol.charAt(0)) && isValidProtocolChars(protocol); - } - - private void computeInitialScheme(String originalUrl) { - for (int i = start; i < end; i++) { - char c = originalUrl.charAt(i); - if (c == ':') { - String s = originalUrl.substring(start, i); - if (isValidProtocol(s)) { - scheme = s.toLowerCase(); - start = i + 1; - } - break; - } else if (c == '/') - break; - } - } - - private boolean overrideWithContext(UriComponents context, String originalUrl) { - - boolean isRelative = false; - - // only use context if the schemes match - if (context != null && (scheme == null || scheme.equalsIgnoreCase(context.getScheme()))) { - - // see RFC2396 5.2.3 - String contextPath = context.getPath(); - if (isNotEmpty(contextPath) && contextPath.charAt(0) == '/') - scheme = null; - - if (scheme == null) { - scheme = context.getScheme(); - userInfo = context.getUserInfo(); - host = context.getHost(); - port = context.getPort(); - path = contextPath; - isRelative = true; - } - } - return isRelative; - } - - private void computeFragment(String originalUrl) { - int charpPosition = originalUrl.indexOf('#', start); - if (charpPosition >= 0) { - end = charpPosition; - } - } - - private void inheritContextQuery(UriComponents context, boolean isRelative) { - // see RFC2396 5.2.2: query and fragment inheritance - if (isRelative && start == end) { - query = context.getQuery(); - } - } - - private boolean splitUrlAndQuery(String originalUrl) { - boolean queryOnly = false; - urlWithoutQuery = originalUrl; - if (start < end) { - int askPosition = originalUrl.indexOf('?'); - queryOnly = askPosition == start; - if (askPosition != -1 && askPosition < end) { - query = originalUrl.substring(askPosition + 1, end); - if (end > askPosition) - end = askPosition; - urlWithoutQuery = originalUrl.substring(0, askPosition); - } - } - - return queryOnly; - } - - private boolean currentPositionStartsWith4Slashes() { - return urlWithoutQuery.regionMatches(start, "////", 0, 4); - } - - private boolean currentPositionStartsWith2Slashes() { - return urlWithoutQuery.regionMatches(start, "//", 0, 2); - } - - private void computeAuthority() { - int authorityEndPosition = urlWithoutQuery.indexOf('/', start); - if (authorityEndPosition < 0) { - authorityEndPosition = urlWithoutQuery.indexOf('?', start); - if (authorityEndPosition < 0) - authorityEndPosition = end; - } - host = authority = urlWithoutQuery.substring(start, authorityEndPosition); - start = authorityEndPosition; - } - - private void computeUserInfo() { - int atPosition = authority.indexOf('@'); - if (atPosition != -1) { - userInfo = authority.substring(0, atPosition); - host = authority.substring(atPosition + 1); - } else - userInfo = null; - } - - private boolean isMaybeIPV6() { - // If the host is surrounded by [ and ] then its an IPv6 - // literal address as specified in RFC2732 - return host.length() > 0 && host.charAt(0) == '['; - } - - private void computeIPV6() { - int positionAfterClosingSquareBrace = host.indexOf(']') + 1; - if (positionAfterClosingSquareBrace > 1) { - - port = -1; - - if (host.length() > positionAfterClosingSquareBrace) { - if (host.charAt(positionAfterClosingSquareBrace) == ':') { - // see RFC2396: port can be null - int portPosition = positionAfterClosingSquareBrace + 1; - if (host.length() > portPosition) { - port = Integer.parseInt(host.substring(portPosition)); - } - } else - throw new IllegalArgumentException("Invalid authority field: " + authority); - } - - host = host.substring(0, positionAfterClosingSquareBrace); - - } else - throw new IllegalArgumentException("Invalid authority field: " + authority); - } - - private void computeRegularHostPort() { - int colonPosition = host.indexOf(':'); - port = -1; - if (colonPosition >= 0) { - // see RFC2396: port can be null - int portPosition = colonPosition + 1; - if (host.length() > portPosition) - port = Integer.parseInt(host.substring(portPosition)); - host = host.substring(0, colonPosition); - } - } - - // /./ - private void removeEmbeddedDot() { - path = path.replace("/./", "/"); - } - - // /../ - private void removeEmbedded2Dots() { - int i = 0; - while ((i = path.indexOf("/../", i)) >= 0) { - if (i > 0) { - end = path.lastIndexOf('/', i - 1); - if (end >= 0 && path.indexOf("/../", end) != 0) { - path = path.substring(0, end) + path.substring(i + 3); - i = 0; - } - } else - i = i + 3; - } - } - - private void removeTailing2Dots() { - while (path.endsWith("/..")) { - end = path.lastIndexOf('/', path.length() - 4); - if (end >= 0) - path = path.substring(0, end + 1); - else - break; - } - } - - private void removeStartingDot() { - if (path.startsWith("./") && path.length() > 2) - path = path.substring(2); - } - - private void removeTrailingDot() { - if (path.endsWith("/.")) - path = path.substring(0, path.length() - 1); - } - - private void initRelativePath() { - int lastSlashPosition = path.lastIndexOf('/'); - String pathEnd = urlWithoutQuery.substring(start, end); - - if (lastSlashPosition == -1) - path = authority != null ? "/" + pathEnd : pathEnd; - else - path = path.substring(0, lastSlashPosition + 1) + pathEnd; - } - - private void handlePathDots() { - if (path.indexOf('.') != -1) { - removeEmbeddedDot(); - removeEmbedded2Dots(); - removeTailing2Dots(); - removeStartingDot(); - removeTrailingDot(); - } - } - - private void parseAuthority() { - if (!currentPositionStartsWith4Slashes() && currentPositionStartsWith2Slashes()) { - start += 2; - - computeAuthority(); - computeUserInfo(); - - if (host != null) { - if (isMaybeIPV6()) - computeIPV6(); - else - computeRegularHostPort(); - } - - if (port < -1) - throw new IllegalArgumentException("Invalid port number :" + port); - - // see RFC2396 5.2.4: ignore context path if authority is defined - if (isNotEmpty(authority)) - path = ""; - } - } - - private void handleRelativePath() { - initRelativePath(); - handlePathDots(); - } - - private void computeRegularPath() { - if (urlWithoutQuery.charAt(start) == '/') - path = urlWithoutQuery.substring(start, end); - - else if (isNotEmpty(path)) - handleRelativePath(); - - else { - String pathEnd = urlWithoutQuery.substring(start, end); - path = authority != null ? "/" + pathEnd : pathEnd; - } - } - - private void computeQueryOnlyPath() { - int lastSlashPosition = path.lastIndexOf('/'); - path = lastSlashPosition < 0 ? "/" : path.substring(0, lastSlashPosition) + "/"; - } - - private void computePath(boolean queryOnly) { - // Parse the file path if any - if (start < end) - computeRegularPath(); - else if (queryOnly && path != null) - computeQueryOnlyPath(); - else if (path == null) - path = ""; - } - - public void parse(UriComponents context, final String originalUrl) { - - if (originalUrl == null) - throw new NullPointerException("originalUrl"); - - boolean isRelative = false; - - trimRight(originalUrl); - trimLeft(originalUrl); - if (!isFragmentOnly(originalUrl)) - computeInitialScheme(originalUrl); - overrideWithContext(context, originalUrl); - computeFragment(originalUrl); - inheritContextQuery(context, isRelative); - - boolean queryOnly = splitUrlAndQuery(originalUrl); - parseAuthority(); - computePath(queryOnly); - } - - private static boolean isNotEmpty(String string) { - return string != null && string.length() > 0; - } -} \ No newline at end of file diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java deleted file mode 100644 index 46371cb8ac..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ /dev/null @@ -1,144 +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.util; - -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.util.List; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.Request; -import org.asynchttpclient.uri.UriComponents; - -/** - * {@link org.asynchttpclient.AsyncHttpProvider} common utilities. - */ -public class AsyncHttpProviderUtils { - - public static final IOException REMOTELY_CLOSED_EXCEPTION = new IOException("Remotely Closed"); - - static { - REMOTELY_CLOSED_EXCEPTION.setStackTrace(new StackTraceElement[] {}); - } - - private final static byte[] NO_BYTES = new byte[0]; - - public final static Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1; - - public static final void validateSupportedScheme(UriComponents uri) { - final String scheme = uri.getScheme(); - if (scheme == null || !scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https") && !scheme.equalsIgnoreCase("ws") - && !scheme.equalsIgnoreCase("wss")) { - throw new IllegalArgumentException("The URI scheme, of the URI " + uri - + ", must be equal (ignoring case) to 'http', 'https', 'ws', or 'wss'"); - } - } - - /** - * @param bodyParts NON EMPTY body part - * @param maxLen - * @return - * @throws UnsupportedEncodingException - */ - public final static byte[] contentToBytes(List bodyParts, int maxLen) throws UnsupportedEncodingException { - final int partCount = bodyParts.size(); - if (partCount == 0) { - return NO_BYTES; - } - if (partCount == 1) { - byte[] chunk = bodyParts.get(0).getBodyPartBytes(); - if (chunk.length <= maxLen) { - return chunk; - } - byte[] result = new byte[maxLen]; - System.arraycopy(chunk, 0, result, 0, maxLen); - return result; - } - int size = 0; - byte[] result = new byte[maxLen]; - for (HttpResponseBodyPart part : bodyParts) { - byte[] chunk = part.getBodyPartBytes(); - int amount = Math.min(maxLen - size, chunk.length); - System.arraycopy(chunk, 0, result, size, amount); - size += amount; - if (size == maxLen) { - return result; - } - } - if (size < maxLen) { - byte[] old = result; - result = new byte[old.length]; - System.arraycopy(old, 0, result, 0, old.length); - } - return result; - } - - public final static String getBaseUrl(UriComponents uri) { - return uri.getScheme() + "://" + getAuthority(uri); - } - - public final static String getAuthority(UriComponents uri) { - int port = uri.getPort() != -1? uri.getPort() : getDefaultPort(uri); - return uri.getHost() + ":" + port; - } - - public final static int getDefaultPort(UriComponents uri) { - int port = uri.getPort(); - if (port == -1) - port = uri.getScheme().equals("http") || uri.getScheme().equals("ws") ? 80 : 443; - return port; - } - - /** - * Convenient for HTTP layer when targeting server root - * - * @return the raw path or "/" if it's null - */ - public final static String getNonEmptyPath(UriComponents uri) { - return isNonEmpty(uri.getPath()) ? uri.getPath() : "/"; - } - - public static String parseCharset(String contentType) { - for (String part : contentType.split(";")) { - if (part.trim().startsWith("charset=")) { - String[] val = part.split("="); - if (val.length > 1) { - String charset = val[1].trim(); - // Quite a lot of sites have charset="CHARSET", - // e.g. charset="utf-8". Note the quotes. This is - // not correct, but client should be able to handle - // it (all browsers do, Grizzly strips it by default) - // This is a poor man's trim("\"").trim("'") - return charset.replaceAll("\"", "").replaceAll("'", ""); - } - } - } - return null; - } - - public static String keepAliveHeaderValue(AsyncHttpClientConfig config) { - return config.isAllowPoolingConnections() ? "keep-alive" : "close"; - } - - public static int requestTimeout(AsyncHttpClientConfig config, Request request) { - return request.getRequestTimeoutInMs() != 0 ? request.getRequestTimeoutInMs() : config.getRequestTimeout(); - } - - public static boolean followRedirect(AsyncHttpClientConfig config, Request request) { - return request.getFollowRedirect() != null? request.getFollowRedirect().booleanValue() : config.isFollowRedirect(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java b/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java deleted file mode 100644 index 0da1666af1..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2010-2013 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.util; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getNonEmptyPath; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Realm; -import org.asynchttpclient.uri.UriComponents; - -import java.security.NoSuchAlgorithmException; - -public final class AuthenticatorUtils { - - public static String computeBasicAuthentication(Realm realm) { - String s = realm.getPrincipal() + ":" + realm.getPassword(); - return "Basic " + Base64.encode(s.getBytes(realm.getCharset())); - } - - public static String computeBasicAuthentication(ProxyServer proxyServer) { - String s = proxyServer.getPrincipal() + ":" + proxyServer.getPassword(); - return "Basic " + Base64.encode(s.getBytes(proxyServer.getCharset())); - } - - private static String computeRealmURI(Realm realm) { - if (realm.isTargetProxy()) { - return "/"; - } else { - UriComponents uri = realm.getUri(); - if (realm.isUseAbsoluteURI()) { - return realm.isOmitQuery() && MiscUtils.isNonEmpty(uri.getQuery()) ? uri.withNewQuery(null).toUrl() : uri.toUrl(); - } else { - String path = getNonEmptyPath(uri); - return realm.isOmitQuery() || !MiscUtils.isNonEmpty(uri.getQuery()) ? path : path + "?" + uri.getQuery(); - } - } - } - - public static String computeDigestAuthentication(Realm realm) throws NoSuchAlgorithmException { - - StringBuilder builder = new StringBuilder().append("Digest "); - append(builder, "username", realm.getPrincipal(), true); - append(builder, "realm", realm.getRealmName(), true); - append(builder, "nonce", realm.getNonce(), true); - append(builder, "uri", computeRealmURI(realm), true); - append(builder, "algorithm", realm.getAlgorithm(), false); - - append(builder, "response", realm.getResponse(), true); - if (isNonEmpty(realm.getOpaque())) - append(builder, "opaque", realm.getOpaque(), true); - append(builder, "qop", realm.getQop(), false); - append(builder, "nc", realm.getNc(), false); - append(builder, "cnonce", realm.getCnonce(), true); - builder.setLength(builder.length() - 2); // remove tailing ", " - - return new String(builder.toString().getBytes(StandardCharsets.ISO_8859_1)); - } - - private static StringBuilder append(StringBuilder builder, String name, String value, boolean quoted) { - builder.append(name).append('='); - if (quoted) - builder.append('"').append(value).append('"'); - else - builder.append(value); - - return builder.append(", "); - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/Base64.java b/api/src/main/java/org/asynchttpclient/util/Base64.java deleted file mode 100644 index c38ace7e39..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/Base64.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.util; - -/** - * Implements the "base64" binary encoding scheme as defined by - * RFC 2045. - *

- * Portions of code here are taken from Apache Pivot - */ -public final class Base64 { - private static final char[] lookup = new char[64]; - private static final byte[] reverseLookup = new byte[256]; - - static { - // Populate the lookup array - - for (int i = 0; i < 26; i++) { - lookup[i] = (char) ('A' + i); - } - - for (int i = 26, j = 0; i < 52; i++, j++) { - lookup[i] = (char) ('a' + j); - } - - for (int i = 52, j = 0; i < 62; i++, j++) { - lookup[i] = (char) ('0' + j); - } - - lookup[62] = '+'; - lookup[63] = '/'; - - // Populate the reverse lookup array - - for (int i = 0; i < 256; i++) { - reverseLookup[i] = -1; - } - - for (int i = 'Z'; i >= 'A'; i--) { - reverseLookup[i] = (byte) (i - 'A'); - } - - for (int i = 'z'; i >= 'a'; i--) { - reverseLookup[i] = (byte) (i - 'a' + 26); - } - - for (int i = '9'; i >= '0'; i--) { - reverseLookup[i] = (byte) (i - '0' + 52); - } - - reverseLookup['+'] = 62; - reverseLookup['/'] = 63; - reverseLookup['='] = 0; - } - - /** - * This class is not instantiable. - */ - private Base64() { - } - - /** - * Encodes the specified data into a base64 string. - * - * @param bytes The unencoded raw data. - */ - public static String encode(byte[] bytes) { - // always sequence of 4 characters for each 3 bytes; padded with '='s as necessary: - StringBuilder buf = new StringBuilder(((bytes.length + 2) / 3) * 4); - - // first, handle complete chunks (fast loop) - int i = 0; - for (int end = bytes.length - 2; i < end;) { - int chunk = ((bytes[i++] & 0xFF) << 16) | ((bytes[i++] & 0xFF) << 8) | (bytes[i++] & 0xFF); - buf.append(lookup[chunk >> 18]); - buf.append(lookup[(chunk >> 12) & 0x3F]); - buf.append(lookup[(chunk >> 6) & 0x3F]); - buf.append(lookup[chunk & 0x3F]); - } - - // then leftovers, if any - int len = bytes.length; - if (i < len) { // 1 or 2 extra bytes? - int chunk = ((bytes[i++] & 0xFF) << 16); - buf.append(lookup[chunk >> 18]); - if (i < len) { // 2 bytes - chunk |= ((bytes[i] & 0xFF) << 8); - buf.append(lookup[(chunk >> 12) & 0x3F]); - buf.append(lookup[(chunk >> 6) & 0x3F]); - } else { // 1 byte - buf.append(lookup[(chunk >> 12) & 0x3F]); - buf.append('='); - } - buf.append('='); - } - return buf.toString(); - } - - /** - * Decodes the specified base64 string back into its raw data. - * - * @param encoded The base64 encoded string. - */ - public static byte[] decode(String encoded) { - int padding = 0; - - for (int i = encoded.length() - 1; encoded.charAt(i) == '='; i--) { - padding++; - } - - int length = encoded.length() * 6 / 8 - padding; - byte[] bytes = new byte[length]; - - for (int i = 0, index = 0, n = encoded.length(); i < n; i += 4) { - int word = reverseLookup[encoded.charAt(i)] << 18; - word += reverseLookup[encoded.charAt(i + 1)] << 12; - word += reverseLookup[encoded.charAt(i + 2)] << 6; - word += reverseLookup[encoded.charAt(i + 3)]; - - for (int j = 0; j < 3 && index + j < length; j++) { - bytes[index + j] = (byte) (word >> (8 * (2 - j))); - } - - index += 3; - } - - return bytes; - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/DateUtils.java b/api/src/main/java/org/asynchttpclient/util/DateUtils.java deleted file mode 100644 index c0b2d49c65..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/DateUtils.java +++ /dev/null @@ -1,23 +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.util; - -public final class DateUtils { - - private DateUtils() { - } - - public static long millisTime() { - return System.nanoTime() / 1000000; - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/DefaultHostnameVerifier.java b/api/src/main/java/org/asynchttpclient/util/DefaultHostnameVerifier.java deleted file mode 100644 index 35f188ca93..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/DefaultHostnameVerifier.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * To the extent possible under law, Kevin Locke has waived all copyright and - * related or neighboring rights to this work. - *

- * A legal description of this waiver is available in LICENSE.txt - */ -package org.asynchttpclient.util; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import javax.security.auth.kerberos.KerberosPrincipal; -import java.security.Principal; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Uses the internal HostnameChecker to verify the server's hostname matches with the - * certificate. This is a requirement for HTTPS, but the raw SSLEngine does not have - * this functionality. As such, it has to be added in manually. For a more complete - * description of hostname verification and why it's important, - * please read - * Fixing - * Hostname Verification. - *

- * This code is based on Kevin Locke's guide . - *

- */ -public class DefaultHostnameVerifier implements HostnameVerifier { - - private HostnameChecker checker; - - private HostnameVerifier extraHostnameVerifier; - - // Logger to log exceptions. - private static final Logger log = Logger.getLogger(DefaultHostnameVerifier.class.getName()); - - /** - * A hostname verifier that uses the {{sun.security.util.HostnameChecker}} under the hood. - */ - public DefaultHostnameVerifier() { - this.checker = new ProxyHostnameChecker(); - } - - /** - * A hostname verifier that takes an external hostname checker. Useful for testing. - * - * @param checker a hostnamechecker. - */ - public DefaultHostnameVerifier(HostnameChecker checker) { - this.checker = checker; - } - - /** - * A hostname verifier that falls back to another hostname verifier if not found. - * - * @param extraHostnameVerifier another hostname verifier. - */ - public DefaultHostnameVerifier(HostnameVerifier extraHostnameVerifier) { - this.checker = new ProxyHostnameChecker(); - this.extraHostnameVerifier = extraHostnameVerifier; - } - - /** - * A hostname verifier with a hostname checker, that falls back to another hostname verifier if not found. - * - * @param checker a custom HostnameChecker. - * @param extraHostnameVerifier another hostname verifier. - */ - public DefaultHostnameVerifier(HostnameChecker checker, HostnameVerifier extraHostnameVerifier) { - this.checker = checker; - this.extraHostnameVerifier = extraHostnameVerifier; - } - - /** - * Matches the hostname against the peer certificate in the session. - * - * @param hostname the IP address or hostname of the expected server. - * @param session the SSL session containing the certificates with the ACTUAL hostname/ipaddress. - * @return true if the hostname matches, false otherwise. - */ - private boolean hostnameMatches(String hostname, SSLSession session) { - log.log(Level.FINE, "hostname = {0}, session = {1}", new Object[] { hostname, Base64.encode(session.getId()) }); - - try { - final Certificate[] peerCertificates = session.getPeerCertificates(); - if (peerCertificates.length == 0) { - log.log(Level.FINE, "No peer certificates"); - return false; - } - - if (peerCertificates[0] instanceof X509Certificate) { - X509Certificate peerCertificate = (X509Certificate) peerCertificates[0]; - log.log(Level.FINE, "peerCertificate = {0}", peerCertificate); - try { - checker.match(hostname, peerCertificate); - // Certificate matches hostname if no exception is thrown. - return true; - } catch (CertificateException ex) { - log.log(Level.FINE, "Certificate does not match hostname", ex); - } - } else { - log.log(Level.FINE, "Peer does not have any certificates or they aren't X.509"); - } - return false; - } catch (SSLPeerUnverifiedException ex) { - log.log(Level.FINE, "Not using certificates for peers, try verifying the principal"); - try { - Principal peerPrincipal = session.getPeerPrincipal(); - log.log(Level.FINE, "peerPrincipal = {0}", peerPrincipal); - if (peerPrincipal instanceof KerberosPrincipal) { - return checker.match(hostname, (KerberosPrincipal) peerPrincipal); - } else { - log.log(Level.FINE, "Can't verify principal, not Kerberos"); - } - } catch (SSLPeerUnverifiedException ex2) { - // Can't verify principal, no principal - log.log(Level.FINE, "Can't verify principal, no principal", ex2); - } - return false; - } - } - - /** - * Verifies the hostname against the peer certificates in a session. Falls back to extraHostnameVerifier if - * there is no match. - * - * @param hostname the IP address or hostname of the expected server. - * @param session the SSL session containing the certificates with the ACTUAL hostname/ipaddress. - * @return true if the hostname matches, false otherwise. - */ - public boolean verify(String hostname, SSLSession session) { - if (hostnameMatches(hostname, session)) { - return true; - } else { - return extraHostnameVerifier != null && extraHostnameVerifier.verify(hostname, session); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/HostnameChecker.java b/api/src/main/java/org/asynchttpclient/util/HostnameChecker.java deleted file mode 100644 index a25bb467e0..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/HostnameChecker.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) Will Sargent. 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.util; - -import java.security.Principal; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -/** - * Hostname checker interface. - */ -public interface HostnameChecker { - - void match(String hostname, X509Certificate peerCertificate) throws CertificateException; - - boolean match(String hostname, Principal principal); -} diff --git a/api/src/main/java/org/asynchttpclient/util/MiscUtils.java b/api/src/main/java/org/asynchttpclient/util/MiscUtils.java deleted file mode 100644 index 55b5ba12cd..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/MiscUtils.java +++ /dev/null @@ -1,61 +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.util; - -import java.io.Closeable; -import java.io.IOException; -import java.util.Collection; -import java.util.Map; - -public class MiscUtils { - - private MiscUtils() { - } - - public static boolean isNonEmpty(String string) { - return string != null && !string.isEmpty(); - } - - public static boolean isNonEmpty(Object[] array) { - return array != null && array.length != 0; - } - - public static boolean isNonEmpty(byte[] array) { - return array != null && array.length != 0; - } - - public static boolean isNonEmpty(Collection collection) { - return collection != null && !collection.isEmpty(); - } - - public static boolean isNonEmpty(Map map) { - return map != null && !map.isEmpty(); - } - - public static boolean getBoolean(String systemPropName, boolean defaultValue) { - String systemPropValue = System.getProperty(systemPropName); - return systemPropValue != null ? systemPropValue.equalsIgnoreCase("true") : defaultValue; - } - - public static T withDefault(T value, T defaults) { - return value != null ? value : value; - } - - public static void closeSilently(Closeable closeable) { - if (closeable != null) - try { - closeable.close(); - } catch (IOException e) { - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/ProxyHostnameChecker.java b/api/src/main/java/org/asynchttpclient/util/ProxyHostnameChecker.java deleted file mode 100644 index 3796911298..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/ProxyHostnameChecker.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) Will Sargent. 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.util; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.security.Principal; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -/** - * A HostnameChecker proxy. - */ -public class ProxyHostnameChecker implements HostnameChecker { - - public final static byte TYPE_TLS = 1; - - private final Object checker = getHostnameChecker(); - - public ProxyHostnameChecker() { - } - - private Object getHostnameChecker() { - final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - try { - @SuppressWarnings("unchecked") - final Class hostnameCheckerClass = (Class) classLoader.loadClass("sun.security.util.HostnameChecker"); - final Method instanceMethod = hostnameCheckerClass.getMethod("getInstance", Byte.TYPE); - return instanceMethod.invoke(null, TYPE_TLS); - } catch (ClassNotFoundException e) { - throw new IllegalStateException(e); - } catch (NoSuchMethodException e) { - throw new IllegalStateException(e); - } catch (InvocationTargetException e) { - throw new IllegalStateException(e); - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } - } - - public void match(String hostname, X509Certificate peerCertificate) throws CertificateException { - try { - final Class hostnameCheckerClass = checker.getClass(); - final Method checkMethod = hostnameCheckerClass.getMethod("match", String.class, X509Certificate.class); - checkMethod.invoke(checker, hostname, peerCertificate); - } catch (NoSuchMethodException e) { - throw new IllegalStateException(e); - } catch (InvocationTargetException e) { - Throwable t = e.getCause(); - if (t instanceof CertificateException) { - throw (CertificateException) t; - } else { - throw new IllegalStateException(e); - } - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } - } - - public boolean match(String hostname, Principal principal) { - try { - final Class hostnameCheckerClass = checker.getClass(); - final Method checkMethod = hostnameCheckerClass.getMethod("match", String.class, Principal.class); - return (Boolean) checkMethod.invoke(null, hostname, principal); - } catch (NoSuchMethodException e) { - throw new IllegalStateException(e); - } catch (InvocationTargetException e) { - throw new IllegalStateException(e); - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } - } - -} diff --git a/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java b/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java deleted file mode 100644 index b80f5e605e..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java +++ /dev/null @@ -1,250 +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.util; - -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.ProxyServer.Protocol; -import org.asynchttpclient.ProxyServerSelector; -import org.asynchttpclient.Request; -import org.asynchttpclient.uri.UriComponents; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.ProxySelector; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.List; -import java.util.Properties; - -/** - * Utilities for Proxy handling. - * - * @author cstamas - */ -public final class ProxyUtils { - - private final static Logger log = LoggerFactory.getLogger(ProxyUtils.class); - - private static final String PROPERTY_PREFIX = "org.asynchttpclient.AsyncHttpClientConfig.proxy."; - - /** - * The host to use as proxy. - */ - public static final String PROXY_HOST = "http.proxyHost"; - - /** - * The port to use for the proxy. - */ - public static final String PROXY_PORT = "http.proxyPort"; - - /** - * The protocol to use. Is mapped to the {@link Protocol} enum. - */ - public static final String PROXY_PROTOCOL = PROPERTY_PREFIX + "protocol"; - - /** - * A specification of non-proxy hosts. See http://download.oracle.com/javase/1.4.2/docs/guide/net/properties.html - */ - public static final String PROXY_NONPROXYHOSTS = "http.nonProxyHosts"; - - /** - * The username to use for authentication for the proxy server. - */ - public static final String PROXY_USER = PROPERTY_PREFIX + "user"; - - /** - * The password to use for authentication for the proxy server. - */ - public static final String PROXY_PASSWORD = PROPERTY_PREFIX + "password"; - - private ProxyUtils() { - } - - /** - * @param config the global config - * @param request the request - * @return the proxy server to be used for this request (can be null) - */ - public static ProxyServer getProxyServer(AsyncHttpClientConfig config, Request request) { - ProxyServer proxyServer = request.getProxyServer(); - if (proxyServer == null) { - ProxyServerSelector selector = config.getProxyServerSelector(); - if (selector != null) { - proxyServer = selector.select(request.getURI()); - } - } - return ProxyUtils.avoidProxy(proxyServer, request) ? null : proxyServer; - } - - /** - * @see #avoidProxy(ProxyServer, String) - */ - public static boolean avoidProxy(final ProxyServer proxyServer, final Request request) { - return avoidProxy(proxyServer, request.getURI().getHost()); - } - - private static boolean matchNonProxyHost(String targetHost, String nonProxyHost) { - - if (nonProxyHost.length() > 1) { - if (nonProxyHost.charAt(0) == '*') - return targetHost.regionMatches(true, targetHost.length() - nonProxyHost.length() + 1, nonProxyHost, 1, - nonProxyHost.length() - 1); - else if (nonProxyHost.charAt(nonProxyHost.length() - 1) == '*') - return targetHost.regionMatches(true, 0, nonProxyHost, 0, nonProxyHost.length() - 1); - } - - return nonProxyHost.equalsIgnoreCase(targetHost); - } - - /** - * Checks whether proxy should be used according to nonProxyHosts settings of it, or we want to go directly to - * target host. If null proxy is passed in, this method returns true -- since there is NO proxy, we - * should avoid to use it. Simple hostname pattern matching using "*" are supported, but only as prefixes. - * See http://download.oracle.com/javase/1.4.2/docs/guide/net/properties.html - * - * @param proxyServer - * @param hostname the hostname - * @return true if we have to avoid proxy use (obeying non-proxy hosts settings), false otherwise. - */ - public static boolean avoidProxy(final ProxyServer proxyServer, final String hostname) { - if (proxyServer != null) { - if (hostname == null) - throw new NullPointerException("hostname"); - - List nonProxyHosts = proxyServer.getNonProxyHosts(); - - if (isNonEmpty(nonProxyHosts)) { - for (String nonProxyHost : nonProxyHosts) { - if (matchNonProxyHost(hostname, nonProxyHost)) - return true; - } - } - - return false; - } else { - return true; - } - } - - /** - * Creates a proxy server instance from the given properties. - *

- * 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. - * @see Networking Properties - * @see #PROXY_HOST - * @see #PROXY_PORT - * @see #PROXY_PROTOCOL - * @see #PROXY_NONPROXYHOSTS - */ - public static ProxyServerSelector createProxyServerSelector(Properties properties) { - String host = properties.getProperty(PROXY_HOST); - - if (host != null) { - int port = Integer.valueOf(properties.getProperty(PROXY_PORT, "80")); - - Protocol protocol; - try { - protocol = Protocol.valueOf(properties.getProperty(PROXY_PROTOCOL, "HTTP")); - } catch (IllegalArgumentException e) { - protocol = Protocol.HTTP; - } - - ProxyServer proxyServer = new ProxyServer(protocol, host, port, properties.getProperty(PROXY_USER), - properties.getProperty(PROXY_PASSWORD)); - - String nonProxyHosts = properties.getProperty(PROXY_NONPROXYHOSTS); - if (nonProxyHosts != null) { - for (String spec : nonProxyHosts.split("\\|")) { - proxyServer.addNonProxyHost(spec); - } - } - - return createProxyServerSelector(proxyServer); - } - - return ProxyServerSelector.NO_PROXY_SELECTOR; - } - - /** - * Get a proxy server selector based on the JDK default proxy selector. - * - * @return The proxy server selector. - */ - public static ProxyServerSelector getJdkDefaultProxyServerSelector() { - return createProxyServerSelector(ProxySelector.getDefault()); - } - - /** - * Create a proxy server selector based on the passed in JDK proxy selector. - * - * @param proxySelector The proxy selector to use. Must not be null. - * @return The proxy server selector. - */ - public static ProxyServerSelector createProxyServerSelector(final ProxySelector proxySelector) { - return new ProxyServerSelector() { - public ProxyServer select(UriComponents uri) { - try { - URI javaUri = uri.toURI(); - - List proxies = proxySelector.select(javaUri); - if (proxies != null) { - // Loop through them until we find one that we know how to use - for (Proxy proxy : proxies) { - switch (proxy.type()) { - case HTTP: - if (!(proxy.address() instanceof InetSocketAddress)) { - log.warn("Don't know how to connect to address " + proxy.address()); - return null; - } else { - InetSocketAddress address = (InetSocketAddress) proxy.address(); - return new ProxyServer(Protocol.HTTP, address.getHostName(), address.getPort()); - } - case DIRECT: - return null; - default: - log.warn("ProxySelector returned proxy type that we don't know how to use: " + proxy.type()); - break; - } - } - } - return null; - } catch (URISyntaxException e) { - log.warn(uri + " couldn't be turned into a java.net.URI", e); - return null; - } - } - }; - } - - /** - * Create a proxy server selector that always selects a single proxy server. - * - * @param proxyServer The proxy server to select. - * @return The proxy server selector. - */ - public static ProxyServerSelector createProxyServerSelector(final ProxyServer proxyServer) { - return new ProxyServerSelector() { - public ProxyServer select(UriComponents uri) { - return proxyServer; - } - }; - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/QueryComputer.java b/api/src/main/java/org/asynchttpclient/util/QueryComputer.java deleted file mode 100644 index 1480dd479d..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/QueryComputer.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.util; - -import java.util.List; - -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.Param; - -public enum QueryComputer { - - URL_ENCODING_ENABLED_QUERY_COMPUTER { - - private final void encodeAndAppendQueryParam(final StringBuilder sb, final CharSequence name, final CharSequence value) { - UTF8UrlEncoder.appendEncoded(sb, name); - if (value != null) { - sb.append('='); - UTF8UrlEncoder.appendEncoded(sb, value); - } - sb.append('&'); - } - - private final void encodeAndAppendQueryParams(final StringBuilder sb, final List queryParams) { - for (Param param : queryParams) - encodeAndAppendQueryParam(sb, param.getName(), param.getValue()); - } - - // FIXME this could be improved: remove split - private final void encodeAndAppendQuery(final StringBuilder sb, final String query) { - int pos; - for (String queryParamString : query.split("&")) { - pos = queryParamString.indexOf('='); - if (pos <= 0) { - CharSequence decodedName = UTF8UrlDecoder.decode(queryParamString); - encodeAndAppendQueryParam(sb, decodedName, null); - } else { - CharSequence decodedName = UTF8UrlDecoder.decode(queryParamString, 0, pos); - int valueStart = pos + 1; - CharSequence decodedValue = UTF8UrlDecoder.decode(queryParamString, valueStart, queryParamString.length() - valueStart); - encodeAndAppendQueryParam(sb, decodedName, decodedValue); - } - } - } - - protected final String withQueryWithParams(final String query, final List queryParams) { - // concatenate encoded query + encoded query params - StringBuilder sb = new StringBuilder(query.length() + queryParams.size() * 16); - encodeAndAppendQuery(sb, query); - encodeAndAppendQueryParams(sb, queryParams); - sb.setLength(sb.length() - 1); - return sb.toString(); - } - - protected final String withQueryWithoutParams(final String query) { - // encode query - StringBuilder sb = new StringBuilder(query.length() + 6); - encodeAndAppendQuery(sb, query); - sb.setLength(sb.length() - 1); - return sb.toString(); - } - - protected final String withoutQueryWithParams(final List queryParams) { - // concatenate encoded query params - StringBuilder sb = new StringBuilder(queryParams.size() * 16); - encodeAndAppendQueryParams(sb, queryParams); - sb.setLength(sb.length() - 1); - return sb.toString(); - } - }, // - - URL_ENCODING_DISABLED_QUERY_COMPUTER { - - private final void appendRawQueryParam(StringBuilder sb, String name, String value) { - sb.append(name); - if (value != null) - sb.append('=').append(value); - sb.append('&'); - } - - private final void appendRawQueryParams(final StringBuilder sb, final List queryParams) { - for (Param param : queryParams) - appendRawQueryParam(sb, param.getName(), param.getValue()); - } - - protected final String withQueryWithParams(final String query, final List queryParams) { - // concatenate raw query + raw query params - StringBuilder sb = new StringBuilder(query.length() + queryParams.size() * 16); - sb.append(query); - appendRawQueryParams(sb, queryParams); - sb.setLength(sb.length() - 1); - return sb.toString(); - } - - protected final String withQueryWithoutParams(final String query) { - // return raw query as is - return query; - } - - protected final String withoutQueryWithParams(final List queryParams) { - // concatenate raw queryParams - StringBuilder sb = new StringBuilder(queryParams.size() * 16); - appendRawQueryParams(sb, queryParams); - sb.setLength(sb.length() - 1); - return sb.toString(); - } - }; - - public static QueryComputer queryComputer(boolean disableUrlEncoding) { - return disableUrlEncoding ? URL_ENCODING_DISABLED_QUERY_COMPUTER : URL_ENCODING_ENABLED_QUERY_COMPUTER; - } - - protected abstract String withQueryWithParams(final String query, final List queryParams); - - protected abstract String withQueryWithoutParams(final String query); - - protected abstract String withoutQueryWithParams(final List queryParams); - - private final String withQuery(final String query, final List queryParams) { - return isNonEmpty(queryParams) ? withQueryWithParams(query, queryParams) : withQueryWithoutParams(query); - } - - private final String withoutQuery(final List queryParams) { - return isNonEmpty(queryParams) ? withoutQueryWithParams(queryParams) : null; - } - - public final String computeFullQueryString(final String query, final List queryParams) { - return isNonEmpty(query) ? withQuery(query, queryParams) : withoutQuery(queryParams); - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/SslUtils.java b/api/src/main/java/org/asynchttpclient/util/SslUtils.java deleted file mode 100644 index becb9ed943..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/SslUtils.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.util; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; - -/** - * This class is a copy of http://github.com/sonatype/wagon-ning/raw/master/src/main/java/org/apache/maven/wagon/providers/http/SslUtils.java - */ -public class SslUtils { - - static class LooseTrustManager implements X509TrustManager { - - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return new java.security.cert.X509Certificate[0]; - } - - public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { - } - - public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { - } - } - - private SSLContext looseTrustManagerSSLContext = looseTrustManagerSSLContext(); - - private SSLContext looseTrustManagerSSLContext() { - try { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, new TrustManager[] { new LooseTrustManager() }, new SecureRandom()); - return sslContext; - } catch (NoSuchAlgorithmException e) { - throw new ExceptionInInitializerError(e); - } catch (KeyManagementException e) { - throw new ExceptionInInitializerError(e); - } - } - - private static class SingletonHolder { - public static final SslUtils instance = new SslUtils(); - } - - public static SslUtils getInstance() { - return SingletonHolder.instance; - } - - public SSLContext getSSLContext(boolean acceptAnyCertificate) throws GeneralSecurityException, IOException { - return acceptAnyCertificate? looseTrustManagerSSLContext: SSLContext.getDefault(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/StandardCharsets.java b/api/src/main/java/org/asynchttpclient/util/StandardCharsets.java deleted file mode 100644 index 13eb64a40e..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/StandardCharsets.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.util; - -import java.nio.charset.Charset; - -public final class StandardCharsets { - - public static final Charset US_ASCII = Charset.forName("US-ASCII"); - public static final Charset UTF_8 = Charset.forName("UTF-8"); - public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); - public static final Charset UNICODE_LITTLE_UNMARKED = Charset.forName("UnicodeLittleUnmarked"); - - private StandardCharsets() { - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/StringCharSequence.java b/api/src/main/java/org/asynchttpclient/util/StringCharSequence.java deleted file mode 100644 index a1cf2192f2..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/StringCharSequence.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.util; - -/** - * A CharSequence String wrapper that doesn't copy the char[] (damn new String implementation!!!) - * - * @author slandelle - */ -public class StringCharSequence implements CharSequence { - - private final String value; - private final int offset; - public final int length; - - public StringCharSequence(String value, int offset, int length) { - this.value = value; - this.offset = offset; - this.length = length; - } - - @Override - public int length() { - return length; - } - - @Override - public char charAt(int index) { - return value.charAt(offset + index); - } - - @Override - public CharSequence subSequence(int start, int end) { - int offsetedEnd = offset + end; - if (offsetedEnd < length) - throw new ArrayIndexOutOfBoundsException(); - return new StringCharSequence(value, offset + start, end - start); - } - - @Override - public String toString() { - return value.substring(offset, length); - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/UTF8UrlDecoder.java b/api/src/main/java/org/asynchttpclient/util/UTF8UrlDecoder.java deleted file mode 100644 index 7184aea114..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/UTF8UrlDecoder.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.util; - -public final class UTF8UrlDecoder { - - private UTF8UrlDecoder() { - } - - private static StringBuilder initSb(StringBuilder sb, String s, int i, int offset, int length) { - if (sb != null) { - return sb; - } else { - int initialSbLength = length > 500 ? length / 2 : length; - return new StringBuilder(initialSbLength).append(s, offset, i); - } - } - - private static int hexaDigit(char c) { - return Character.digit(c, 16); - } - - public static CharSequence decode(String s) { - return decode(s, 0, s.length()); - } - - public static CharSequence decode(final String s, final int offset, final int length) { - - StringBuilder sb = null; - int i = offset; - int end = length + offset; - - while (i < end) { - char c = s.charAt(i); - if (c == '+') { - sb = initSb(sb, s, i, offset, length); - sb.append(' '); - i++; - - } else if (c == '%') { - if (end - i < 3) // We expect 3 chars. 0 based i vs. 1 based length! - throw new IllegalArgumentException("UTF8UrlDecoder: Incomplete trailing escape (%) pattern"); - - int x, y; - if ((x = hexaDigit(s.charAt(i + 1))) == -1 || (y = hexaDigit(s.charAt(i + 2))) == -1) - throw new IllegalArgumentException("UTF8UrlDecoder: Malformed"); - - sb = initSb(sb, s, i, offset, length); - sb.append((char) (x * 16 + y)); - i += 3; - } else { - if (sb != null) - sb.append(c); - i++; - } - } - - return sb != null ? sb.toString() : new StringCharSequence(s, offset, length); - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/UTF8UrlEncoder.java b/api/src/main/java/org/asynchttpclient/util/UTF8UrlEncoder.java deleted file mode 100644 index 78cb74d9e0..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/UTF8UrlEncoder.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.util; - -/** - * Convenience class that encapsulates details of "percent encoding" - * (as per RFC-3986, see [http://www.ietf.org/rfc/rfc3986.txt]). - */ -public class UTF8UrlEncoder { - private static final boolean encodeSpaceUsingPlus = System.getProperty("com.UTF8UrlEncoder.encodeSpaceUsingPlus") != null; - - /** - * Encoding table used for figuring out ascii characters that must be escaped - * (all non-Ascii characters need to be encoded anyway) - */ - private final static boolean[] SAFE_ASCII = new boolean[128]; - - static { - for (int i = 'a'; i <= 'z'; ++i) { - SAFE_ASCII[i] = true; - } - for (int i = 'A'; i <= 'Z'; ++i) { - SAFE_ASCII[i] = true; - } - for (int i = '0'; i <= '9'; ++i) { - SAFE_ASCII[i] = true; - } - SAFE_ASCII['-'] = true; - SAFE_ASCII['.'] = true; - SAFE_ASCII['_'] = true; - SAFE_ASCII['~'] = true; - } - - private static final char[] HEX = "0123456789ABCDEF".toCharArray(); - - private UTF8UrlEncoder() { - } - - public static String encode(String input) { - StringBuilder sb = new StringBuilder(input.length() + 16); - appendEncoded(sb, input); - return sb.toString(); - } - - public static StringBuilder appendEncoded(StringBuilder sb, CharSequence input) { - int c; - for (int i = 0; i < input.length(); i+= Character.charCount(c)) { - c = Character.codePointAt(input, i); - if (c <= 127) - if (SAFE_ASCII[c]) - sb.append((char) c); - else - appendSingleByteEncoded(sb, c); - else - appendMultiByteEncoded(sb, c); - } - return sb; - } - - private final static void appendSingleByteEncoded(StringBuilder sb, int value) { - - if (encodeSpaceUsingPlus && value == 32) { - sb.append('+'); - return; - } - - sb.append('%'); - sb.append(HEX[value >> 4]); - sb.append(HEX[value & 0xF]); - } - - private final static void appendMultiByteEncoded(StringBuilder sb, int value) { - if (value < 0x800) { - appendSingleByteEncoded(sb, (0xc0 | (value >> 6))); - appendSingleByteEncoded(sb, (0x80 | (value & 0x3f))); - } else if (value < 0x10000) { - appendSingleByteEncoded(sb, (0xe0 | (value >> 12))); - appendSingleByteEncoded(sb, (0x80 | ((value >> 6) & 0x3f))); - appendSingleByteEncoded(sb, (0x80 | (value & 0x3f))); - } else { - appendSingleByteEncoded(sb, (0xf0 | (value >> 18))); - appendSingleByteEncoded(sb, (0x80 | (value >> 12) & 0x3f)); - appendSingleByteEncoded(sb, (0x80 | (value >> 6) & 0x3f)); - appendSingleByteEncoded(sb, (0x80 | (value & 0x3f))); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java b/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java deleted file mode 100644 index 6d3b0e0a60..0000000000 --- a/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java +++ /dev/null @@ -1,295 +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 java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.asynchttpclient.AsyncCompletionHandlerBase; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.uri.UriComponents; -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; - -/** - * Simple {@link AsyncHandler} that add support for WebDav's response manipulation. - * - * @param - */ -public abstract class WebDavCompletionHandlerBase implements AsyncHandler { - private final Logger logger = LoggerFactory.getLogger(AsyncCompletionHandlerBase.class); - - private final List bodies = Collections.synchronizedList(new ArrayList()); - private HttpResponseStatus status; - private HttpResponseHeaders headers; - - /** - * {@inheritDoc} - */ - @Override - public final STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { - bodies.add(content); - return STATE.CONTINUE; - } - - /** - * {@inheritDoc} - */ - @Override - public final STATE onStatusReceived(final HttpResponseStatus status) throws Exception { - this.status = status; - return STATE.CONTINUE; - } - - /** - * {@inheritDoc} - */ - @Override - public final STATE onHeadersReceived(final HttpResponseHeaders headers) throws Exception { - this.headers = headers; - return STATE.CONTINUE; - } - - /** - * {@inheritDoc} - */ - @Override - public final T onCompleted() throws Exception { - if (status != null) { - Response response = status.prepareResponse(headers, bodies); - Document document = null; - if (status.getStatusCode() == 207) { - document = readXMLResponse(response.getResponseBodyAsStream()); - } - return onCompleted(new WebDavResponse(status.prepareResponse(headers, bodies), document)); - } else { - throw new IllegalStateException("Status is null"); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void onThrowable(Throwable t) { - logger.debug(t.getMessage(), t); - } - - /** - * Invoked once the HTTP response has been fully read. - * - * @param response The {@link org.asynchttpclient.Response} - * @return Type of the value that will be returned by the associated {@link java.util.concurrent.Future} - */ - abstract public T onCompleted(WebDavResponse response) throws Exception; - - private class HttpStatusWrapper extends HttpResponseStatus { - - private final HttpResponseStatus wrapped; - - private final String statusText; - - private final int statusCode; - - public HttpStatusWrapper(HttpResponseStatus wrapper, String statusText, int statusCode) { - super(wrapper.getUri(), null); - this.wrapped = wrapper; - this.statusText = statusText; - this.statusCode = statusCode; - } - - @Override - public Response prepareResponse(HttpResponseHeaders headers, List bodyParts) { - final Response wrappedResponse = wrapped.prepareResponse(headers, bodyParts); - - return new Response() { - - @Override - public int getStatusCode() { - return statusCode; - } - - @Override - public String getStatusText() { - return statusText; - } - - @Override - public byte[] getResponseBodyAsBytes() throws IOException { - return wrappedResponse.getResponseBodyAsBytes(); - } - - @Override - public ByteBuffer getResponseBodyAsByteBuffer() throws IOException { - return wrappedResponse.getResponseBodyAsByteBuffer(); - } - - @Override - public InputStream getResponseBodyAsStream() throws IOException { - return wrappedResponse.getResponseBodyAsStream(); - } - - @Override - public String getResponseBodyExcerpt(int maxLength, String charset) throws IOException { - return wrappedResponse.getResponseBodyExcerpt(maxLength, charset); - } - - @Override - public String getResponseBody(String charset) throws IOException { - return wrappedResponse.getResponseBody(charset); - } - - @Override - public String getResponseBodyExcerpt(int maxLength) throws IOException { - return wrappedResponse.getResponseBodyExcerpt(maxLength); - } - - @Override - public String getResponseBody() throws IOException { - return wrappedResponse.getResponseBody(); - } - - @Override - public UriComponents getUri() { - return wrappedResponse.getUri(); - } - - @Override - public String getContentType() { - return wrappedResponse.getContentType(); - } - - @Override - public String getHeader(String name) { - return wrappedResponse.getHeader(name); - } - - @Override - public List getHeaders(String name) { - return wrappedResponse.getHeaders(name); - } - - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return wrappedResponse.getHeaders(); - } - - @Override - public boolean isRedirected() { - return wrappedResponse.isRedirected(); - } - - @Override - public List getCookies() { - return wrappedResponse.getCookies(); - } - - @Override - public boolean hasResponseStatus() { - return wrappedResponse.hasResponseStatus(); - } - - @Override - public boolean hasResponseHeaders() { - return wrappedResponse.hasResponseHeaders(); - } - - @Override - public boolean hasResponseBody() { - return wrappedResponse.hasResponseBody(); - } - }; - } - - @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(); - } - } - - private Document readXMLResponse(InputStream stream) { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - Document document = null; - try { - document = factory.newDocumentBuilder().parse(stream); - parse(document); - } catch (SAXException e) { - logger.error(e.getMessage(), e); - throw new RuntimeException(e); - } catch (IOException e) { - logger.error(e.getMessage(), e); - throw new RuntimeException(e); - } catch (ParserConfigurationException e) { - logger.error(e.getMessage(), e); - throw new RuntimeException(e); - } - return document; - } - - private void parse(Document document) { - 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.valueOf(value.substring(value.indexOf(" "), value.lastIndexOf(" ")).trim()); - String statusText = value.substring(value.lastIndexOf(" ")); - status = new HttpStatusWrapper(status, statusText, statusCode); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java b/api/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java deleted file mode 100644 index cbeb2ad0b1..0000000000 --- a/api/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java +++ /dev/null @@ -1,119 +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 java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.List; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.Response; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.uri.UriComponents; -import org.w3c.dom.Document; - -/** - * 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 Document document; - - public WebDavResponse(Response response, Document document) { - this.response = response; - this.document = document; - } - - public int getStatusCode() { - return response.getStatusCode(); - } - - public String getStatusText() { - return response.getStatusText(); - } - - @Override - public byte[] getResponseBodyAsBytes() throws IOException { - return response.getResponseBodyAsBytes(); - } - - public ByteBuffer getResponseBodyAsByteBuffer() throws IOException { - return response.getResponseBodyAsByteBuffer(); - } - - public InputStream getResponseBodyAsStream() throws IOException { - return response.getResponseBodyAsStream(); - } - - public String getResponseBodyExcerpt(int maxLength) throws IOException { - return response.getResponseBodyExcerpt(maxLength); - } - - public String getResponseBodyExcerpt(int maxLength, String charset) throws IOException { - return response.getResponseBodyExcerpt(maxLength, charset); - } - - public String getResponseBody() throws IOException { - return response.getResponseBody(); - } - - public String getResponseBody(String charset) throws IOException { - return response.getResponseBody(charset); - } - - public UriComponents getUri() { - return response.getUri(); - } - - public String getContentType() { - return response.getContentType(); - } - - public String getHeader(String name) { - return response.getHeader(name); - } - - public List getHeaders(String name) { - return response.getHeaders(name); - } - - public FluentCaseInsensitiveStringsMap getHeaders() { - return response.getHeaders(); - } - - public boolean isRedirected() { - return response.isRedirected(); - } - - public List getCookies() { - return response.getCookies(); - } - - public boolean hasResponseStatus() { - return response.hasResponseStatus(); - } - - public boolean hasResponseHeaders() { - return response.hasResponseHeaders(); - } - - public boolean hasResponseBody() { - return response.hasResponseBody(); - } - - public Document getBodyAsXML() { - return document; - } -} diff --git a/api/src/main/java/org/asynchttpclient/websocket/DefaultWebSocketListener.java b/api/src/main/java/org/asynchttpclient/websocket/DefaultWebSocketListener.java deleted file mode 100644 index 7e465acffa..0000000000 --- a/api/src/main/java/org/asynchttpclient/websocket/DefaultWebSocketListener.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2012-2013 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.websocket; - -/** - * Default WebSocketListener implementation. Most methods are no-ops. This - * allows for quick override customization without clutter of methods that the - * developer isn't interested in dealing with. - * - * @since 1.7.0 - */ -public class DefaultWebSocketListener implements WebSocketByteListener, WebSocketTextListener, WebSocketPingListener, WebSocketPongListener { - - protected WebSocket webSocket; - - // -------------------------------------- Methods from WebSocketByteListener - - /** - * {@inheritDoc} - */ - @Override - public void onMessage(byte[] message) { - } - - // -------------------------------------- Methods from WebSocketPingListener - - /** - * {@inheritDoc} - */ - @Override - public void onPing(byte[] message) { - } - - // -------------------------------------- Methods from WebSocketPongListener - - /** - * {@inheritDoc} - */ - @Override - public void onPong(byte[] message) { - } - - // -------------------------------------- Methods from WebSocketTextListener - - /** - * {@inheritDoc} - */ - @Override - public void onMessage(String message) { - } - - // ------------------------------------------ Methods from WebSocketListener - - /** - * {@inheritDoc} - */ - @Override - public void onOpen(WebSocket websocket) { - this.webSocket = websocket; - } - - /** - * {@inheritDoc} - */ - @Override - public void onClose(WebSocket websocket) { - this.webSocket = null; - } - - /** - * {@inheritDoc} - */ - @Override - public void onError(Throwable t) { - } -} diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocket.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocket.java deleted file mode 100644 index 81f7392b99..0000000000 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocket.java +++ /dev/null @@ -1,111 +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.websocket; - -import java.io.Closeable; - -/** - * A Websocket client - */ -public interface WebSocket extends Closeable { - - /** - * Send a byte message. - * @param message a byte message - * @return this - */ - WebSocket sendMessage(byte[] message); - - /** - * Allows streaming of multiple binary fragments. - * - * @param fragment binary fragment. - * @param last flag indicating whether or not this is the last fragment. - * - * @return this. - */ - WebSocket stream(byte[] fragment, boolean last); - - /** - * Allows streaming of multiple binary fragments. - * - * @param fragment binary fragment. - * @param offset starting offset. - * @param len length. - * @param last flag indicating whether or not this is the last fragment. - * @return this. - */ - WebSocket stream(byte[] fragment, int offset, int len, boolean last); - - /** - * Send a text message - * @param message a text message - * @return this. - */ - WebSocket sendTextMessage(String message); - - /** - * Allows streaming of multiple text fragments. - * - * @param fragment text fragment. - * @param last flag indicating whether or not this is the last fragment. - * @return this. - */ - WebSocket streamText(String fragment, boolean last); - - /** - * Send a ping with an optional payload - * (limited to 125 bytes or less). - * - * @param payload the ping payload. - * - * @return this. - */ - WebSocket sendPing(byte[] payload); - - /** - * Send a ping with an optional payload - * (limited to 125 bytes or less). - * - * @param payload the pong payload. - * @return this. - */ - WebSocket sendPong(byte[] payload); - - /** - * Add a {@link WebSocketListener} - * @param l a {@link WebSocketListener} - * @return this - */ - WebSocket addWebSocketListener(WebSocketListener l); - - /** - * Add a {@link WebSocketListener} - * @param l a {@link WebSocketListener} - * @return this - */ - WebSocket removeWebSocketListener(WebSocketListener l); - - /** - * Returns true if the WebSocket is open/connected. - * - * @return true if the WebSocket is open/connected. - */ - boolean isOpen(); - - /** - * Close the WebSocket. - */ - void close(); -} - diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteFragmentListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteFragmentListener.java deleted file mode 100644 index 9988115b25..0000000000 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteFragmentListener.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.websocket; - -import org.asynchttpclient.HttpResponseBodyPart; - -/** - * Invoked when WebSocket binary fragments are received. - * - * @param fragment text fragment - */ -public interface WebSocketByteFragmentListener extends WebSocketListener { - - void onFragment(HttpResponseBodyPart fragment); -} diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteListener.java deleted file mode 100644 index 99fb4cede9..0000000000 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteListener.java +++ /dev/null @@ -1,26 +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.websocket; - -/** - * A {@link WebSocketListener} for bytes - */ -public interface WebSocketByteListener extends WebSocketListener { - - /** - * Invoked when bytes are available. - * - * @param message a byte array. - */ - void onMessage(byte[] message); -} diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketCloseCodeReasonListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketCloseCodeReasonListener.java deleted file mode 100644 index ad1b25f0bb..0000000000 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketCloseCodeReasonListener.java +++ /dev/null @@ -1,27 +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.websocket; - -/** - * Extend the normal close listener with one that support the WebSocket's code and reason. - * @see "http://tools.ietf.org/html/rfc6455#section-5.5.1" - */ -public interface WebSocketCloseCodeReasonListener { - - /** - * Invoked when the {@link WebSocket} is close. - * - * @param websocket - */ - void onClose(WebSocket websocket, int code, String reason); -} diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketListener.java deleted file mode 100644 index 6c8b7fbc25..0000000000 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketListener.java +++ /dev/null @@ -1,40 +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.websocket; - -/** - * A generic {@link WebSocketListener} for WebSocket events. Use the appropriate listener for receiving message bytes. - */ -public interface WebSocketListener { - - /** - * Invoked when the {@link WebSocket} is open. - * - * @param websocket - */ - void onOpen(WebSocket websocket); - - /** - * Invoked when the {@link WebSocket} is close. - * - * @param websocket - */ - void onClose(WebSocket websocket); - - /** - * Invoked when the {@link WebSocket} is open. - * - * @param t a {@link Throwable} - */ - void onError(Throwable t); -} diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketPingListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketPingListener.java deleted file mode 100644 index 0567c90c2d..0000000000 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketPingListener.java +++ /dev/null @@ -1,25 +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.websocket; - -/** - * A WebSocket's Ping Listener - */ -public interface WebSocketPingListener extends WebSocketListener { - - /** - * Invoked when a ping message is received - * @param message a byte array - */ - void onPing(byte[] message); -} diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketPongListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketPongListener.java deleted file mode 100644 index a27e93bfd4..0000000000 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketPongListener.java +++ /dev/null @@ -1,25 +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.websocket; - -/** - * A WebSocket's Pong Listener - */ -public interface WebSocketPongListener extends WebSocketListener { - - /** - * Invoked when a pong message is received - * @param message a byte array - */ - void onPong(byte[] message); -} diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextFragmentListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextFragmentListener.java deleted file mode 100644 index a71f9364b4..0000000000 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextFragmentListener.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.websocket; - -import org.asynchttpclient.HttpResponseBodyPart; - -/** - * Invoked when WebSocket text fragments are received. - * - * @param fragment text fragment - */ -public interface WebSocketTextFragmentListener extends WebSocketListener { - - void onFragment(HttpResponseBodyPart fragment); -} diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextListener.java deleted file mode 100644 index 7f8242c74d..0000000000 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextListener.java +++ /dev/null @@ -1,25 +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.websocket; - -/** - * A {@link WebSocketListener} for text message - */ -public interface WebSocketTextListener extends WebSocketListener { - - /** - * Invoked when WebSocket text message are received. - * @param message a {@link String} message - */ - void onMessage(String message); -} diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java deleted file mode 100644 index b612c9b0ac..0000000000 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java +++ /dev/null @@ -1,183 +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.websocket; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.UpgradeHandler; - -/** - * An {@link AsyncHandler} which is able to execute WebSocket upgrade. Use the Builder for configuring WebSocket options. - */ -public class WebSocketUpgradeHandler implements UpgradeHandler, AsyncHandler { - - private WebSocket webSocket; - private final List listeners; - private final AtomicBoolean ok = new AtomicBoolean(false); - private boolean onSuccessCalled; - private int status; - - public WebSocketUpgradeHandler(List listeners) { - this.listeners = listeners; - } - - /** - * {@inheritDoc} - */ - @Override - public final void onThrowable(Throwable t) { - onFailure(t); - } - - public boolean touchSuccess() { - boolean prev = onSuccessCalled; - onSuccessCalled = true; - return prev; - } - - /** - * {@inheritDoc} - */ - @Override - public final STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - return STATE.CONTINUE; - } - - /** - * {@inheritDoc} - */ - @Override - public final STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { - status = responseStatus.getStatusCode(); - if (responseStatus.getStatusCode() == 101) { - return STATE.UPGRADE; - } else { - return STATE.ABORT; - } - } - - /** - * {@inheritDoc} - */ - @Override - public final STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { - return STATE.CONTINUE; - } - - /** - * {@inheritDoc} - */ - @Override - public final WebSocket onCompleted() throws Exception { - - if (status != 101) { - IllegalStateException e = new IllegalStateException("Invalid Status Code " + status); - for (WebSocketListener listener : listeners) { - listener.onError(e); - } - return null; - } - - if (webSocket == null) { - throw new IllegalStateException("WebSocket is null"); - } - return webSocket; - } - - /** - * {@inheritDoc} - */ - @Override - public final void onSuccess(WebSocket webSocket) { - this.webSocket = webSocket; - for (WebSocketListener listener : listeners) { - webSocket.addWebSocketListener(listener); - listener.onOpen(webSocket); - } - ok.set(true); - } - - /** - * {@inheritDoc} - */ - @Override - public final void onFailure(Throwable t) { - for (WebSocketListener listener : listeners) { - if (!ok.get() && webSocket != null) { - webSocket.addWebSocketListener(listener); - } - listener.onError(t); - } - } - - public final void onClose(WebSocket webSocket, int status, String reasonPhrase) { - // Connect failure - if (this.webSocket == null) - this.webSocket = webSocket; - - for (WebSocketListener listener : listeners) { - if (webSocket != null) { - webSocket.addWebSocketListener(listener); - } - listener.onClose(webSocket); - if (listener instanceof WebSocketCloseCodeReasonListener) { - WebSocketCloseCodeReasonListener.class.cast(listener).onClose(webSocket, status, reasonPhrase); - } - } - } - - /** - * Build a {@link WebSocketUpgradeHandler} - */ - public final static class Builder { - - private List listeners = new ArrayList(); - - /** - * Add a {@link WebSocketListener} that will be added to the {@link WebSocket} - * - * @param listener a {@link WebSocketListener} - * @return this - */ - public Builder addWebSocketListener(WebSocketListener listener) { - listeners.add(listener); - return this; - } - - /** - * Remove a {@link WebSocketListener} - * - * @param listener a {@link WebSocketListener} - * @return this - */ - public Builder removeWebSocketListener(WebSocketListener listener) { - listeners.remove(listener); - return this; - } - - /** - * Build a {@link WebSocketUpgradeHandler} - * - * @return a {@link WebSocketUpgradeHandler} - */ - public WebSocketUpgradeHandler build() { - return new WebSocketUpgradeHandler(listeners); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/RealmTest.java b/api/src/test/java/org/asynchttpclient/RealmTest.java deleted file mode 100644 index 23315a92e6..0000000000 --- a/api/src/test/java/org/asynchttpclient/RealmTest.java +++ /dev/null @@ -1,125 +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; - -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.Realm.AuthScheme; -import org.asynchttpclient.Realm.RealmBuilder; -import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.StandardCharsets; -import org.testng.annotations.Test; - -import java.math.BigInteger; -import java.security.MessageDigest; - -public class RealmTest { - @Test(groups = "fast") - public void testClone() { - RealmBuilder builder = new RealmBuilder(); - builder.setPrincipal("user").setPassword("pass"); - builder.setEncoding(StandardCharsets.UTF_8.name()).setUsePreemptiveAuth(true); - builder.setRealmName("realm").setAlgorithm("algo"); - builder.setScheme(AuthScheme.BASIC); - Realm orig = builder.build(); - - Realm clone = new RealmBuilder().clone(orig).build(); - assertEquals(clone.getPrincipal(), orig.getPrincipal()); - assertEquals(clone.getPassword(), orig.getPassword()); - assertEquals(clone.getEncoding(), orig.getEncoding()); - assertEquals(clone.getUsePreemptiveAuth(), orig.getUsePreemptiveAuth()); - assertEquals(clone.getRealmName(), orig.getRealmName()); - assertEquals(clone.getAlgorithm(), orig.getAlgorithm()); - assertEquals(clone.getAuthScheme(), orig.getAuthScheme()); - } - - @Test(groups = "fast") - public void testOldDigestEmptyString() { - String qop = ""; - testOldDigest(qop); - } - - @Test(groups = "fast") - public void testOldDigestNull() { - String qop = null; - testOldDigest(qop); - } - - private void testOldDigest(String qop) { - String user = "user"; - String pass = "pass"; - String realm = "realm"; - String nonce = "nonce"; - String method = "GET"; - UriComponents uri = UriComponents.create("http://ahc.io/foo"); - RealmBuilder builder = new RealmBuilder(); - builder.setPrincipal(user).setPassword(pass); - builder.setNonce(nonce); - builder.setUri(uri); - builder.setMethodName(method); - builder.setRealmName(realm); - builder.setQop(qop); - builder.setScheme(AuthScheme.DIGEST); - Realm orig = builder.build(); - - String ha1 = getMd5(user + ":" + realm + ":" + pass); - String ha2 = getMd5(method + ":" + uri); - String expectedResponse = getMd5(ha1 + ":" + nonce + ":" + ha2); - - assertEquals(expectedResponse, orig.getResponse()); - } - - @Test(groups = "fast") - public void testStrongDigest() { - String user = "user"; - String pass = "pass"; - String realm = "realm"; - String nonce = "nonce"; - String method = "GET"; - UriComponents uri = UriComponents.create("http://ahc.io/foo"); - String qop = "auth"; - RealmBuilder builder = new RealmBuilder(); - builder.setPrincipal(user).setPassword(pass); - builder.setNonce(nonce); - builder.setUri(uri); - builder.setMethodName(method); - builder.setRealmName(realm); - builder.setQop(qop); - builder.setScheme(AuthScheme.DIGEST); - Realm orig = builder.build(); - - String nc = orig.getNc(); - String cnonce = orig.getCnonce(); - String ha1 = getMd5(user + ":" + realm + ":" + pass); - String ha2 = getMd5(method + ":" + uri); - String expectedResponse = getMd5(ha1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2); - - assertEquals(expectedResponse, orig.getResponse()); - } - - private String getMd5(String what) { - try { - MessageDigest md = MessageDigest.getInstance("MD5"); - md.update(what.getBytes("ISO-8859-1")); - byte[] hash = md.digest(); - BigInteger bi = new BigInteger(1, hash); - String result = bi.toString(16); - if (result.length() % 2 != 0) { - return "0" + result; - } - return result; - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/AbstractBasicHttpsTest.java b/api/src/test/java/org/asynchttpclient/async/AbstractBasicHttpsTest.java deleted file mode 100644 index e95112a70c..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/AbstractBasicHttpsTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpsServer; - -import org.testng.annotations.BeforeClass; - -public abstract class AbstractBasicHttpsTest extends AbstractBasicTest { - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - server = newJettyHttpsServer(port1); - server.setHandler(configureHandler()); - server.start(); - logger.info("Local HTTP server started successfully"); - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/AbstractBasicTest.java b/api/src/test/java/org/asynchttpclient/async/AbstractBasicTest.java deleted file mode 100644 index 224b24eb9a..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/AbstractBasicTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.addHttpConnector; -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; -import org.asynchttpclient.async.util.EchoHandler; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; - -public abstract class AbstractBasicTest { - - protected final static int TIMEOUT = 30; - - protected final Logger logger = LoggerFactory.getLogger(getClass()); - - protected Server server; - protected int port1; - protected int port2; - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - - port1 = findFreePort(); - port2 = findFreePort(); - - server = newJettyHttpServer(port1); - server.setHandler(configureHandler()); - addHttpConnector(server, port2); - server.start(); - - logger.info("Local HTTP server started successfully"); - } - - @AfterClass(alwaysRun = true) - public void tearDownGlobal() throws Exception { - if (server != null) - server.stop(); - } - - protected String getTargetUrl() { - return String.format("http://127.0.0.1:%d/foo/test", port1); - } - - protected String getTargetUrl2() { - return String.format("https://127.0.0.1:%d/foo/test", port2); - } - - public AbstractHandler configureHandler() throws Exception { - return new EchoHandler(); - } - - public static class AsyncCompletionHandlerAdapter extends AsyncCompletionHandler { - public Runnable runnable; - - @Override - public Response onCompleted(Response response) throws Exception { - return response; - } - - @Override - public void onThrowable(Throwable t) { - t.printStackTrace(); - fail("Unexpected exception: " + t.getMessage(), t); - } - } - - public static class AsyncHandlerAdapter implements AsyncHandler { - - @Override - public void onThrowable(Throwable t) { - t.printStackTrace(); - fail("Unexpected exception", t); - } - - @Override - public STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { - return STATE.CONTINUE; - } - - @Override - public STATE onStatusReceived(final HttpResponseStatus responseStatus) throws Exception { - return STATE.CONTINUE; - } - - @Override - public STATE onHeadersReceived(final HttpResponseHeaders headers) throws Exception { - return STATE.CONTINUE; - } - - @Override - public String onCompleted() throws Exception { - return ""; - } - } - - public abstract AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config); -} diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java deleted file mode 100755 index b4ae251628..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java +++ /dev/null @@ -1,1600 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.util.DateUtils.millisTime; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpClientConfig.Builder; -import org.asynchttpclient.AsyncHttpClientConfigBean; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.MaxRedirectException; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.multipart.Part; -import org.asynchttpclient.multipart.StringPart; -import org.asynchttpclient.util.StandardCharsets; -import org.testng.annotations.Test; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.ConnectException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.channels.UnresolvedAddressException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -public abstract class AsyncProvidersBasicTest extends AbstractBasicTest { - - protected abstract String acceptEncodingHeader(); - - protected abstract AsyncHttpProviderConfig getProviderConfig(); - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncProviderEncodingTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Request request = new RequestBuilder("GET").setUrl(getTargetUrl() + "?q=+%20x").build(); - assertEquals(request.getURI().toUrl(), getTargetUrl() + "?q=%20%20x"); - - String url = client.executeRequest(request, new AsyncCompletionHandler() { - @Override - public String onCompleted(Response response) throws Exception { - return response.getUri().toString(); - } - - @Override - public void onThrowable(Throwable t) { - t.printStackTrace(); - fail("Unexpected exception: " + t.getMessage(), t); - } - - }).get(); - assertEquals(url, getTargetUrl() + "?q=%20%20x"); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncProviderEncodingTest2() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Request request = new RequestBuilder("GET").setUrl(getTargetUrl() + "").addQueryParam("q", "a b").build(); - - String url = client.executeRequest(request, new AsyncCompletionHandler() { - @Override - public String onCompleted(Response response) throws Exception { - return response.getUri().toString(); - } - - @Override - public void onThrowable(Throwable t) { - t.printStackTrace(); - fail("Unexpected exception: " + t.getMessage(), t); - } - - }).get(); - assertEquals(url, getTargetUrl() + "?q=a%20b"); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void emptyRequestURI() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Request request = new RequestBuilder("GET").setUrl(getTargetUrl()).build(); - - String url = client.executeRequest(request, new AsyncCompletionHandler() { - @Override - public String onCompleted(Response response) throws Exception { - return response.getUri().toString(); - } - - @Override - public void onThrowable(Throwable t) { - t.printStackTrace(); - fail("Unexpected exception: " + t.getMessage(), t); - } - - }).get(); - assertEquals(url, getTargetUrl()); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncProviderContentLenghtGETTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - final HttpURLConnection connection = (HttpURLConnection) new URL(getTargetUrl()).openConnection(); - connection.connect(); - final int ct = connection.getContentLength(); - connection.disconnect(); - try { - final CountDownLatch l = new CountDownLatch(1); - - Request request = new RequestBuilder("GET").setUrl(getTargetUrl()).build(); - client.executeRequest(request, new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - int contentLenght = -1; - if (response.getHeader("content-length") != null) { - contentLenght = Integer.valueOf(response.getHeader("content-length")); - } - assertEquals(contentLenght, ct); - } finally { - l.countDown(); - } - return response; - } - - @Override - public void onThrowable(Throwable t) { - try { - fail("Unexpected exception", t); - } finally { - l.countDown(); - } - } - - }).get(); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncContentTypeGETTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - Request request = new RequestBuilder("GET").setUrl(getTargetUrl()).build(); - client.executeRequest(request, new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getContentType(), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); - } finally { - l.countDown(); - } - return response; - } - }).get(); - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncHeaderGETTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - Request request = new RequestBuilder("GET").setUrl(getTargetUrl()).build(); - client.executeRequest(request, new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getContentType(), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); - } finally { - l.countDown(); - } - return response; - } - }).get(); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncHeaderPOSTTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Test1", "Test1"); - h.add("Test2", "Test2"); - h.add("Test3", "Test3"); - h.add("Test4", "Test4"); - h.add("Test5", "Test5"); - Request request = new RequestBuilder("GET").setUrl(getTargetUrl()).setHeaders(h).build(); - - client.executeRequest(request, new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - for (int i = 1; i < 5; i++) { - assertEquals(response.getHeader("X-Test" + i), "Test" + i); - } - } finally { - l.countDown(); - } - return response; - } - }).get(); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncParamPOSTTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - - Map> m = new HashMap>(); - for (int i = 0; i < 5; i++) { - m.put("param_" + i, Arrays.asList("value_" + i)); - } - Request request = new RequestBuilder("POST").setUrl(getTargetUrl()).setHeaders(h).setFormParams(m).build(); - client.executeRequest(request, new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - for (int i = 1; i < 5; i++) { - assertEquals(response.getHeader("X-param_" + i), "value_" + i); - } - } finally { - l.countDown(); - } - return response; - } - }).get(); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncStatusHEADTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - Request request = new RequestBuilder("HEAD").setUrl(getTargetUrl()).build(); - Response response = client.executeRequest(request, new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - } finally { - l.countDown(); - } - return response; - } - }).get(); - - try { - String s = response.getResponseBody(); - assertEquals("", s); - } catch (IllegalStateException ex) { - fail(); - } - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - // TODO: fix test - @Test(groups = { "standalone", "default_provider", "async" }, enabled = false) - public void asyncStatusHEADContentLenghtTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(120 * 1000).build()); - try { - final CountDownLatch l = new CountDownLatch(1); - Request request = new RequestBuilder("HEAD").setUrl(getTargetUrl()).build(); - - client.executeRequest(request, new AsyncCompletionHandlerAdapter() { - @Override - public Response onCompleted(Response response) throws Exception { - fail(); - return response; - } - - @Override - public void onThrowable(Throwable t) { - try { - assertEquals(t.getClass(), IOException.class); - assertEquals(t.getMessage(), "No response received. Connection timed out"); - } finally { - l.countDown(); - } - - } - }).get(); - - if (!l.await(10 * 5 * 1000, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "online", "default_provider", "async" }, expectedExceptions = { NullPointerException.class }) - public void asyncNullSchemeTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - - try { - client.prepareGet("www.sun.com").execute(); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoGetTransferEncodingTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - - client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("Transfer-Encoding"), "chunked"); - } finally { - l.countDown(); - } - return response; - } - }).get(); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoGetHeadersTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Test1", "Test1"); - h.add("Test2", "Test2"); - h.add("Test3", "Test3"); - h.add("Test4", "Test4"); - h.add("Test5", "Test5"); - client.prepareGet(getTargetUrl()).setHeaders(h).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - for (int i = 1; i < 5; i++) { - assertEquals(response.getHeader("X-Test" + i), "Test" + i); - } - } finally { - l.countDown(); - } - return response; - } - }).get(); - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoGetCookieTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Test1", "Test1"); - h.add("Test2", "Test2"); - h.add("Test3", "Test3"); - h.add("Test4", "Test4"); - h.add("Test5", "Test5"); - - final Cookie coo = Cookie.newValidCookie("foo", "value", "value", "/", "/", -1L, -1, false, false); - client.prepareGet(getTargetUrl()).setHeaders(h).addCookie(coo).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - List cookies = response.getCookies(); - assertEquals(cookies.size(), 1); - assertEquals(cookies.get(0).toString(), "foo=value"); - } finally { - l.countDown(); - } - return response; - } - }).get(); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostDefaultContentType() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - client.preparePost(getTargetUrl()).addFormParam("foo", "bar").execute(new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - FluentCaseInsensitiveStringsMap h = response.getHeaders(); - assertEquals(h.getJoinedValue("X-Content-Type", ", "), "application/x-www-form-urlencoded"); - } finally { - l.countDown(); - } - return response; - } - }).get(); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostBodyIsoTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.preparePost(getTargetUrl()).addHeader("X-ISO", "true").setBody("\u017D\u017D\u017D\u017D\u017D\u017D").execute().get(); - assertEquals(response.getResponseBody().getBytes("ISO-8859-1"), "\u017D\u017D\u017D\u017D\u017D\u017D".getBytes("ISO-8859-1")); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostBytesTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 5; i++) { - sb.append("param_").append(i).append("=value_").append(i).append("&"); - } - sb.setLength(sb.length() - 1); - - client.preparePost(getTargetUrl()).setHeaders(h).setBody(sb.toString()).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - for (int i = 1; i < 5; i++) { - System.out.println(">>>>> " + response.getHeader("X-param_" + i)); - assertEquals(response.getHeader("X-param_" + i), "value_" + i); - - } - } finally { - l.countDown(); - } - return response; - } - }).get(); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostInputStreamTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 5; i++) { - sb.append("param_").append(i).append("=value_").append(i).append("&"); - } - sb.setLength(sb.length() - 1); - ByteArrayInputStream is = new ByteArrayInputStream(sb.toString().getBytes()); - - client.preparePost(getTargetUrl()).setHeaders(h).setBody(is).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - for (int i = 1; i < 5; i++) { - System.out.println(">>>>> " + response.getHeader("X-param_" + i)); - assertEquals(response.getHeader("X-param_" + i), "value_" + i); - - } - } finally { - l.countDown(); - } - return response; - } - }).get(); - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPutInputStreamTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 5; i++) { - sb.append("param_").append(i).append("=value_").append(i).append("&"); - } - sb.setLength(sb.length() - 1); - ByteArrayInputStream is = new ByteArrayInputStream(sb.toString().getBytes()); - - client.preparePut(getTargetUrl()).setHeaders(h).setBody(is).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - for (int i = 1; i < 5; i++) { - assertEquals(response.getHeader("X-param_" + i), "value_" + i); - } - } finally { - l.countDown(); - } - return response; - } - }).get(); - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostMultiPartTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - - Part p = new StringPart("foo", "bar", StandardCharsets.UTF_8); - - client.preparePost(getTargetUrl()).addBodyPart(p).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - String xContentType = response.getHeader("X-Content-Type"); - String boundary = xContentType.substring((xContentType.indexOf("boundary") + "boundary".length() + 1)); - - String s = response.getResponseBodyExcerpt(boundary.length() + "--".length()).substring("--".length()); - assertEquals(boundary, s); - } finally { - l.countDown(); - } - return response; - } - }).get(); - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostBasicGZIPTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setCompressionEnabled(true).build()); - try { - final CountDownLatch l = new CountDownLatch(1); - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 5; i++) { - sb.append("param_").append(i).append("=value_").append(i).append("&"); - } - sb.setLength(sb.length() - 1); - - client.preparePost(getTargetUrl()).setHeaders(h).setBody(sb.toString()).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("X-Accept-Encoding"), acceptEncodingHeader()); - } finally { - l.countDown(); - } - return response; - } - }).get(); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostProxyTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setProxyServer(new ProxyServer("127.0.0.1", port2)).build()); - try { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 5; i++) { - sb.append("param_").append(i).append("=value_").append(i).append("&"); - } - sb.setLength(sb.length() - 1); - - Response response = client.preparePost(getTargetUrl()).setHeaders(h).setBody(sb.toString()).execute(new AsyncCompletionHandler() { - @Override - public Response onCompleted(Response response) throws Exception { - return response; - } - - @Override - public void onThrowable(Throwable t) { - } - }).get(); - - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("X-Connection"), "keep-alive"); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncRequestVirtualServerPOSTTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - - Map> m = new HashMap>(); - for (int i = 0; i < 5; i++) { - m.put("param_" + i, Arrays.asList("value_" + i)); - } - Request request = new RequestBuilder("POST").setUrl(getTargetUrl()).setHeaders(h).setFormParams(m).setVirtualHost("localhost:" + port1).build(); - - Response response = client.executeRequest(request, new AsyncCompletionHandlerAdapter()).get(); - - assertEquals(response.getStatusCode(), 200); - if (response.getHeader("X-Host").startsWith("localhost")) { - assertEquals(response.getHeader("X-Host"), "localhost:" + port1); - } else { - assertEquals(response.getHeader("X-Host"), "127.0.0.1:" + port1); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPutTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 5; i++) { - sb.append("param_").append(i).append("=value_").append(i).append("&"); - } - sb.setLength(sb.length() - 1); - - Response response = client.preparePut(getTargetUrl()).setHeaders(h).setBody(sb.toString()).execute(new AsyncCompletionHandlerAdapter()).get(); - - assertEquals(response.getStatusCode(), 200); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostLatchBytesTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 5; i++) { - sb.append("param_").append(i).append("=value_").append(i).append("&"); - } - sb.setLength(sb.length() - 1); - - c.preparePost(getTargetUrl()).setHeaders(h).setBody(sb.toString()).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - for (int i = 1; i < 5; i++) { - assertEquals(response.getHeader("X-param_" + i), "value_" + i); - } - return response; - } finally { - l.countDown(); - } - } - }); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostDelayCancelTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - h.add("LockThread", "true"); - StringBuilder sb = new StringBuilder(); - sb.append("LockThread=true"); - - Future future = client.preparePost(getTargetUrl()).setHeaders(h).setBody(sb.toString()).execute(new AsyncCompletionHandlerAdapter() { - @Override - public void onThrowable(Throwable t) { - } - }); - future.cancel(true); - Response response = future.get(TIMEOUT, TimeUnit.SECONDS); - assertNull(response); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostDelayBytesTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - h.add("LockThread", "true"); - StringBuilder sb = new StringBuilder(); - sb.append("LockThread=true"); - - try { - Future future = client.preparePost(getTargetUrl()).setHeaders(h).setBody(sb.toString()).execute(new AsyncCompletionHandlerAdapter() { - @Override - public void onThrowable(Throwable t) { - t.printStackTrace(); - } - }); - - future.get(10, TimeUnit.SECONDS); - } catch (ExecutionException ex) { - if (ex.getCause() instanceof TimeoutException) { - assertTrue(true); - } - } catch (TimeoutException te) { - assertTrue(true); - } catch (IllegalStateException ex) { - assertTrue(false); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostNullBytesTest() throws Exception { - - AsyncHttpClient client = getAsyncHttpClient(null); - try { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 5; i++) { - sb.append("param_").append(i).append("=value_").append(i).append("&"); - } - sb.setLength(sb.length() - 1); - - Future future = client.preparePost(getTargetUrl()).setHeaders(h).setBody(sb.toString()).execute(new AsyncCompletionHandlerAdapter()); - - Response response = future.get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostListenerBytesTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 5; i++) { - sb.append("param_").append(i).append("=value_").append(i).append("&"); - } - sb.setLength(sb.length() - 1); - - final CountDownLatch l = new CountDownLatch(1); - - client.preparePost(getTargetUrl()).setHeaders(h).setBody(sb.toString()).execute(new AsyncCompletionHandlerAdapter() { - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - } finally { - l.countDown(); - } - return response; - } - }); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Latch time out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncConnectInvalidFuture() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - int dummyPort = findFreePort(); - final AtomicInteger count = new AtomicInteger(); - for (int i = 0; i < 20; i++) { - try { - Response response = client.preparePost(String.format("http://127.0.0.1:%d/", dummyPort)).execute(new AsyncCompletionHandlerAdapter() { - @Override - public void onThrowable(Throwable t) { - count.incrementAndGet(); - } - }).get(); - assertNull(response, "Should have thrown ExecutionException"); - } catch (ExecutionException ex) { - Throwable cause = ex.getCause(); - if (!(cause instanceof ConnectException)) { - fail("Should have been caused by ConnectException, not by " + cause.getClass().getName()); - } - } - } - assertEquals(count.get(), 20); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncConnectInvalidPortFuture() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - int dummyPort = findFreePort(); - try { - Response response = client.preparePost(String.format("http://127.0.0.1:%d/", dummyPort)).execute(new AsyncCompletionHandlerAdapter() { - @Override - public void onThrowable(Throwable t) { - t.printStackTrace(); - } - }).get(); - assertNull(response, "Should have thrown ExecutionException"); - } catch (ExecutionException ex) { - Throwable cause = ex.getCause(); - if (!(cause instanceof ConnectException)) { - fail("Should have been caused by ConnectException, not by " + cause.getClass().getName()); - } - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncConnectInvalidPort() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - // pick a random unused local port - int port = findFreePort(); - - try { - Response response = client.preparePost(String.format("http://127.0.0.1:%d/", port)).execute(new AsyncCompletionHandlerAdapter() { - @Override - public void onThrowable(Throwable t) { - t.printStackTrace(); - } - }).get(); - assertNull(response, "No ExecutionException was thrown"); - } catch (ExecutionException ex) { - assertEquals(ex.getCause().getClass(), ConnectException.class); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncConnectInvalidHandlerPort() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - int port = findFreePort(); - - client.prepareGet(String.format("http://127.0.0.1:%d/", port)).execute(new AsyncCompletionHandlerAdapter() { - @Override - public void onThrowable(Throwable t) { - try { - assertEquals(t.getClass(), ConnectException.class); - } finally { - l.countDown(); - } - } - }); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timed out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "online", "default_provider", "async" }) - public void asyncConnectInvalidHandlerHost() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - - client.prepareGet("http://null.apache.org:9999/").execute(new AsyncCompletionHandlerAdapter() { - @Override - public void onThrowable(Throwable t) { - if (t != null) { - if (t.getClass().equals(ConnectException.class)) { - l.countDown(); - } else if (t.getClass().equals(UnresolvedAddressException.class)) { - l.countDown(); - } - } - } - }); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timed out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncConnectInvalidFuturePort() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final AtomicBoolean called = new AtomicBoolean(false); - final AtomicBoolean rightCause = new AtomicBoolean(false); - // pick a random unused local port - int port = findFreePort(); - - try { - Response response = client.prepareGet(String.format("http://127.0.0.1:%d/", port)).execute(new AsyncCompletionHandlerAdapter() { - @Override - public void onThrowable(Throwable t) { - called.set(true); - if (t instanceof ConnectException) { - rightCause.set(true); - } - } - }).get(); - assertNull(response, "No ExecutionException was thrown"); - } catch (ExecutionException ex) { - assertEquals(ex.getCause().getClass(), ConnectException.class); - } - assertTrue(called.get(), "onThrowable should get called."); - assertTrue(rightCause.get(), "onThrowable should get called with ConnectionException"); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncContentLenghtGETTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public void onThrowable(Throwable t) { - fail("Unexpected exception", t); - } - }).get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncResponseBodyTooLarge() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.preparePost(getTargetUrl()).setBody("0123456789").execute(new AsyncCompletionHandlerAdapter() { - - @Override - public void onThrowable(Throwable t) { - fail("Unexpected exception", t); - } - }).get(); - - assertNotNull(response.getResponseBodyExcerpt(Integer.MAX_VALUE)); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncResponseEmptyBody() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public void onThrowable(Throwable t) { - fail("Unexpected exception", t); - } - }).get(); - - assertEquals(response.getResponseBody(), ""); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "asyncAPI" }) - public void asyncAPIContentLenghtGETTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - // Use a l in case the assert fail - final CountDownLatch l = new CountDownLatch(1); - - client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - } finally { - l.countDown(); - } - return response; - } - - @Override - public void onThrowable(Throwable t) { - } - }); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timed out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "asyncAPI" }) - public void asyncAPIHandlerExceptionTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - // Use a l in case the assert fail - final CountDownLatch l = new CountDownLatch(1); - - client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerAdapter() { - @Override - public Response onCompleted(Response response) throws Exception { - throw new IllegalStateException("FOO"); - } - - @Override - public void onThrowable(Throwable t) { - try { - if (t.getMessage() != null) { - assertEquals(t.getMessage(), "FOO"); - } - } finally { - l.countDown(); - } - } - }); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timed out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoGetDelayHandlerTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(5 * 1000).build()); - try { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("LockThread", "true"); - - // Use a l in case the assert fail - final CountDownLatch l = new CountDownLatch(1); - - client.prepareGet(getTargetUrl()).setHeaders(h).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - fail("Must not receive a response"); - } finally { - l.countDown(); - } - return response; - } - - @Override - public void onThrowable(Throwable t) { - try { - if (t instanceof TimeoutException) { - assertTrue(true); - } else { - fail("Unexpected exception", t); - } - } finally { - l.countDown(); - } - } - }); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timed out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoGetQueryStringTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - // Use a l in case the assert fail - final CountDownLatch l = new CountDownLatch(1); - - AsyncCompletionHandler handler = new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertTrue(response.getHeader("X-pathInfo") != null); - assertTrue(response.getHeader("X-queryString") != null); - } finally { - l.countDown(); - } - return response; - } - }; - - Request req = new RequestBuilder("GET").setUrl(getTargetUrl() + "?foo=bar").build(); - - client.executeRequest(req, handler).get(); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timed out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoGetKeepAliveHandlerTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - // Use a l in case the assert fail - final CountDownLatch l = new CountDownLatch(2); - - AsyncCompletionHandler handler = new AsyncCompletionHandlerAdapter() { - - String remoteAddr = null; - - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - if (remoteAddr == null) { - remoteAddr = response.getHeader("X-KEEP-ALIVE"); - } else { - assertEquals(response.getHeader("X-KEEP-ALIVE"), remoteAddr); - } - } finally { - l.countDown(); - } - return response; - } - }; - - client.prepareGet(getTargetUrl()).execute(handler).get(); - client.prepareGet(getTargetUrl()).execute(handler); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timed out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "online", "default_provider", "async" }) - public void asyncDoGetMaxRedirectTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new Builder().setMaxRedirects(0).setFollowRedirect(true).build()); - try { - // Use a l in case the assert fail - final CountDownLatch l = new CountDownLatch(1); - - AsyncCompletionHandler handler = new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - fail("Should not be here"); - return response; - } - - @Override - public void onThrowable(Throwable t) { - t.printStackTrace(); - try { - assertEquals(t.getClass(), MaxRedirectException.class); - } finally { - l.countDown(); - } - } - }; - - client.prepareGet("http://google.com/").execute(handler); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timed out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "online", "default_provider", "async" }) - public void asyncDoGetNestedTest() throws Exception { - final AsyncHttpClient client = getAsyncHttpClient(null); - try { - // FIXME find a proper website that redirects the same number of times whatever the language - // Use a l in case the assert fail - final CountDownLatch l = new CountDownLatch(2); - - final AsyncCompletionHandlerAdapter handler = new AsyncCompletionHandlerAdapter() { - - private final static int MAX_NESTED = 2; - - private AtomicInteger nestedCount = new AtomicInteger(0); - - @Override - public Response onCompleted(Response response) throws Exception { - try { - if (nestedCount.getAndIncrement() < MAX_NESTED) { - System.out.println("Executing a nested request: " + nestedCount); - client.prepareGet("http://www.lemonde.fr/").execute(this); - } - } finally { - l.countDown(); - } - return response; - } - - @Override - public void onThrowable(Throwable t) { - t.printStackTrace(); - } - }; - - client.prepareGet("http://www.lemonde.fr/").execute(handler); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timed out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "online", "default_provider", "async" }) - public void asyncDoGetStreamAndBodyTest() throws Exception { - final AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.prepareGet("http://www.lemonde.fr/").execute().get(); - assertEquals(response.getStatusCode(), 200); - } finally { - client.close(); - } - } - - @Test(groups = { "online", "default_provider", "async" }) - public void asyncUrlWithoutPathTest() throws Exception { - final AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.prepareGet("http://www.lemonde.fr/").execute().get(); - assertEquals(response.getStatusCode(), 200); - } finally { - client.close(); - } - } - - @Test(groups = { "default_provider", "async" }) - public void optionsTest() throws Exception { - final AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.prepareOptions(getTargetUrl()).execute().get(); - - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("Allow"), "GET,HEAD,POST,OPTIONS,TRACE"); - } finally { - client.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void testAwsS3() throws Exception { - final AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.prepareGet("http://test.s3.amazonaws.com/").execute().get(); - if (response.getResponseBody() == null || response.getResponseBody().equals("")) { - fail("No response Body"); - } else { - assertEquals(response.getStatusCode(), 403); - } - } finally { - client.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void testAsyncHttpProviderConfig() throws Exception { - - final AsyncHttpClient client = getAsyncHttpClient(new Builder().setAsyncHttpClientProviderConfig(getProviderConfig()).build()); - try { - Response response = client.prepareGet("http://test.s3.amazonaws.com/").execute().get(); - if (response.getResponseBody() == null || response.getResponseBody().equals("")) { - fail("No response Body"); - } else { - assertEquals(response.getStatusCode(), 403); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void idleRequestTimeoutTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setPooledConnectionIdleTimeout(5000).setRequestTimeout(10000).build()); - try { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - h.add("LockThread", "true"); - - long t1 = millisTime(); - try { - client.prepareGet(getTargetUrl()).setHeaders(h).setUrl(getTargetUrl()).execute().get(); - fail(); - } catch (Throwable ex) { - final long elapsedTime = millisTime() - t1; - System.out.println("EXPIRED: " + (elapsedTime)); - assertNotNull(ex.getCause()); - assertTrue(elapsedTime >= 10000 && elapsedTime <= 25000); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostCancelTest() throws Exception { - - AsyncHttpClient client = getAsyncHttpClient(null); - try { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - h.add("LockThread", "true"); - StringBuilder sb = new StringBuilder(); - sb.append("LockThread=true"); - - final AtomicReference ex = new AtomicReference(); - ex.set(null); - try { - Future future = client.preparePost(getTargetUrl()).setHeaders(h).setBody(sb.toString()).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public void onThrowable(Throwable t) { - if (t instanceof CancellationException) { - ex.set((CancellationException) t); - } - t.printStackTrace(); - } - - }); - - future.cancel(true); - } catch (IllegalStateException ise) { - fail(); - } - assertNotNull(ex.get()); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void getShouldAllowBody() throws IOException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - client.prepareGet(getTargetUrl()).setBody("Boo!").execute(); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }, expectedExceptions = { NullPointerException.class }) - public void invalidUri() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - client.prepareGet(String.format("http:127.0.0.1:%d/foo/test", port1)).build(); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncHttpClientConfigBeanTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfigBean().setUserAgent("test")); - try { - Response response = client.executeRequest(client.prepareGet(getTargetUrl()).build()).get(); - assertEquals(200, response.getStatusCode()); - } finally { - client.close(); - } - } - - @Test(groups = { "default_provider", "async" }) - public void bodyAsByteTest() throws Exception { - final AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.prepareGet(getTargetUrl()).execute().get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getResponseBodyAsBytes(), new byte[] {}); - } finally { - client.close(); - } - } - - @Test(groups = { "default_provider", "async" }) - public void mirrorByteTest() throws Exception { - final AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.preparePost(getTargetUrl()).setBody("MIRROR").execute().get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(new String(response.getResponseBodyAsBytes(), StandardCharsets.UTF_8), "MIRROR"); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java deleted file mode 100644 index 4bd4d6fe6a..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java +++ /dev/null @@ -1,519 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.BoundRequestBuilder; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; -import org.testng.annotations.Test; - -import java.util.Arrays; -import java.util.Locale; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -public abstract class AsyncStreamHandlerTest extends AbstractBasicTest { - - private static final String RESPONSE = "param_1_"; - - @Test(groups = { "standalone", "default_provider" }) - public void asyncStreamGETTest() throws Exception { - final CountDownLatch l = new CountDownLatch(1); - AsyncHttpClient c = getAsyncHttpClient(null); - final AtomicReference responseHeaders = new AtomicReference(); - final AtomicReference throwable = new AtomicReference(); - try { - c.prepareGet(getTargetUrl()).execute(new AsyncHandlerAdapter() { - - @Override - public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - try { - responseHeaders.set(content.getHeaders()); - return STATE.ABORT; - } finally { - l.countDown(); - } - } - - @Override - public void onThrowable(Throwable t) { - try { - throwable.set(t); - } finally { - l.countDown(); - } - } - }); - - if (!l.await(5, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - - FluentCaseInsensitiveStringsMap h = responseHeaders.get(); - assertNotNull(h, "No response headers"); - assertEquals(h.getJoinedValue("content-type", ", "), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET, "Unexpected content-type"); - assertNull(throwable.get(), "Unexpected exception"); - - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncStreamPOSTTest() throws Exception { - - final AtomicReference responseHeaders = new AtomicReference(); - - AsyncHttpClient c = getAsyncHttpClient(null); - try { - Future f = c.preparePost(getTargetUrl())// - .setHeader("Content-Type", "application/x-www-form-urlencoded")// - .addFormParam("param_1", "value_1")// - .execute(new AsyncHandlerAdapter() { - private StringBuilder builder = new StringBuilder(); - - @Override - public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - responseHeaders.set(content.getHeaders()); - return STATE.CONTINUE; - } - - @Override - public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { - builder.append(new String(content.getBodyPartBytes())); - return STATE.CONTINUE; - } - - @Override - public String onCompleted() throws Exception { - return builder.toString().trim(); - } - }); - - String responseBody = f.get(10, TimeUnit.SECONDS); - FluentCaseInsensitiveStringsMap h = responseHeaders.get(); - assertNotNull(h); - assertEquals(h.getJoinedValue("content-type", ", "), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); - assertEquals(responseBody, RESPONSE); - - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncStreamInterruptTest() throws Exception { - final CountDownLatch l = new CountDownLatch(1); - - AsyncHttpClient c = getAsyncHttpClient(null); - - final AtomicReference responseHeaders = new AtomicReference(); - final AtomicBoolean bodyReceived = new AtomicBoolean(false); - final AtomicReference throwable = new AtomicReference(); - try { - c.preparePost(getTargetUrl())// - .setHeader("Content-Type", "application/x-www-form-urlencoded")// - .addFormParam("param_1", "value_1")// - .execute(new AsyncHandlerAdapter() { - - @Override - public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - responseHeaders.set(content.getHeaders()); - return STATE.ABORT; - } - - @Override - public STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { - bodyReceived.set(true); - return STATE.ABORT; - } - - @Override - public void onThrowable(Throwable t) { - throwable.set(t); - l.countDown(); - } - }); - - l.await(5, TimeUnit.SECONDS); - assertTrue(!bodyReceived.get(), "Interrupted not working"); - FluentCaseInsensitiveStringsMap h = responseHeaders.get(); - assertNotNull(h, "Should receive non null headers"); - assertEquals(h.getJoinedValue("content-type", ", ").toLowerCase(Locale.ENGLISH), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET.toLowerCase(Locale.ENGLISH), "Unexpected content-type"); - assertNull(throwable.get(), "Should get an exception"); - - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncStreamFutureTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - final AtomicReference responseHeaders = new AtomicReference(); - final AtomicReference throwable = new AtomicReference(); - try { - Future f = c.preparePost(getTargetUrl()).addFormParam("param_1", "value_1").execute(new AsyncHandlerAdapter() { - private StringBuilder builder = new StringBuilder(); - - @Override - public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - responseHeaders.set(content.getHeaders()); - return STATE.CONTINUE; - } - - @Override - public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { - builder.append(new String(content.getBodyPartBytes())); - return STATE.CONTINUE; - } - - @Override - public String onCompleted() throws Exception { - return builder.toString().trim(); - } - - @Override - public void onThrowable(Throwable t) { - throwable.set(t); - } - }); - - String responseBody = f.get(5, TimeUnit.SECONDS); - FluentCaseInsensitiveStringsMap h = responseHeaders.get(); - assertNotNull(h, "Should receive non null headers"); - assertEquals(h.getJoinedValue("content-type", ", ").toLowerCase(Locale.ENGLISH), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET.toLowerCase(Locale.ENGLISH), "Unexpected content-type"); - assertNotNull(responseBody, "No response body"); - assertEquals(responseBody.trim(), RESPONSE, "Unexpected response body"); - assertNull(throwable.get(), "Unexpected exception"); - - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncStreamThrowableRefusedTest() throws Exception { - - final CountDownLatch l = new CountDownLatch(1); - AsyncHttpClient c = getAsyncHttpClient(null); - try { - c.prepareGet(getTargetUrl()).execute(new AsyncHandlerAdapter() { - - @Override - public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - throw new RuntimeException("FOO"); - } - - @Override - public void onThrowable(Throwable t) { - try { - if (t.getMessage() != null) { - assertEquals(t.getMessage(), "FOO"); - } - } finally { - l.countDown(); - } - } - }); - - if (!l.await(10, TimeUnit.SECONDS)) { - fail("Timed out"); - } - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncStreamReusePOSTTest() throws Exception { - - AsyncHttpClient c = getAsyncHttpClient(null); - final AtomicReference responseHeaders = new AtomicReference(); - try { - BoundRequestBuilder rb = c.preparePost(getTargetUrl())// - .setHeader("Content-Type", "application/x-www-form-urlencoded") - .addFormParam("param_1", "value_1"); - - Future f = rb.execute(new AsyncHandlerAdapter() { - private StringBuilder builder = new StringBuilder(); - - @Override - public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - responseHeaders.set(content.getHeaders()); - return STATE.CONTINUE; - } - - @Override - public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { - builder.append(new String(content.getBodyPartBytes())); - return STATE.CONTINUE; - } - - @Override - public String onCompleted() throws Exception { - return builder.toString(); - } - }); - - String r = f.get(5, TimeUnit.SECONDS); - FluentCaseInsensitiveStringsMap h = responseHeaders.get(); - assertNotNull(h, "Should receive non null headers"); - assertEquals(h.getJoinedValue("content-type", ", ").toLowerCase(Locale.ENGLISH), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET.toLowerCase(Locale.ENGLISH), "Unexpected content-type"); - assertNotNull(r, "No response body"); - assertEquals(r.trim(), RESPONSE, "Unexpected response body"); - - responseHeaders.set(null); - - // Let do the same again - f = rb.execute(new AsyncHandlerAdapter() { - private StringBuilder builder = new StringBuilder(); - - @Override - public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - responseHeaders.set(content.getHeaders()); - return STATE.CONTINUE; - } - - @Override - public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { - builder.append(new String(content.getBodyPartBytes())); - return STATE.CONTINUE; - } - - @Override - public String onCompleted() throws Exception { - return builder.toString(); - } - }); - - f.get(5, TimeUnit.SECONDS); - h = responseHeaders.get(); - assertNotNull(h, "Should receive non null headers"); - assertEquals(h.getJoinedValue("content-type", ", ").toLowerCase(Locale.ENGLISH), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET.toLowerCase(Locale.ENGLISH), "Unexpected content-type"); - assertNotNull(r, "No response body"); - assertEquals(r.trim(), RESPONSE, "Unexpected response body"); - } finally { - c.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void asyncStream302RedirectWithBody() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build()); - final AtomicReference statusCode = new AtomicReference(0); - final AtomicReference responseHeaders = new AtomicReference(); - try { - Future f = c.prepareGet("http://google.com/").execute(new AsyncHandlerAdapter() { - - public STATE onStatusReceived(HttpResponseStatus status) throws Exception { - statusCode.set(status.getStatusCode()); - return STATE.CONTINUE; - } - - @Override - public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - responseHeaders.set(content.getHeaders()); - return STATE.CONTINUE; - } - - @Override - public String onCompleted() throws Exception { - return null; - } - }); - - f.get(20, TimeUnit.SECONDS); - assertTrue(statusCode.get() != 302); - FluentCaseInsensitiveStringsMap h = responseHeaders.get(); - assertNotNull(h); - assertEquals(h.getFirstValue("server"), "gws"); - // This assertion below is not an invariant, since implicitly contains locale-dependant settings - // and fails when run in country having own localized Google site and it's locale relies on something - // other than ISO-8859-1. - // In Hungary for example, http://google.com/ redirects to http://www.google.hu/, a localized - // Google site, that uses ISO-8892-2 encoding (default for HU). Similar is true for other - // non-ISO-8859-1 using countries that have "localized" google, like google.hr, google.rs, google.cz, google.sk etc. - // - // assertEquals(h.getJoinedValue("content-type", ", "), "text/html; charset=ISO-8859-1"); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }, timeOut = 3000, description = "Test behavior of 'read only status line' scenario.") - public void asyncStreamJustStatusLine() throws Exception { - final int STATUS = 0; - final int COMPLETED = 1; - final int OTHER = 2; - final boolean[] whatCalled = new boolean[] { false, false, false }; - final CountDownLatch latch = new CountDownLatch(1); - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future statusCode = client.prepareGet(getTargetUrl()).execute(new AsyncHandler() { - private int status = -1; - - @Override - public void onThrowable(Throwable t) { - whatCalled[OTHER] = true; - latch.countDown(); - } - - @Override - public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - whatCalled[OTHER] = true; - latch.countDown(); - return STATE.ABORT; - } - - @Override - public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { - whatCalled[STATUS] = true; - status = responseStatus.getStatusCode(); - latch.countDown(); - return STATE.ABORT; - } - - @Override - public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { - whatCalled[OTHER] = true; - latch.countDown(); - return STATE.ABORT; - } - - @Override - public Integer onCompleted() throws Exception { - whatCalled[COMPLETED] = true; - latch.countDown(); - return status; - } - }); - - if (!latch.await(2, TimeUnit.SECONDS)) { - fail("Timeout"); - return; - } - Integer status = statusCode.get(TIMEOUT, TimeUnit.SECONDS); - assertEquals((int) status, 200, "Expected status code failed."); - - if (!whatCalled[STATUS]) { - fail("onStatusReceived not called."); - } - if (!whatCalled[COMPLETED]) { - fail("onCompleted not called."); - } - if (whatCalled[OTHER]) { - fail("Other method of AsyncHandler got called."); - } - } finally { - client.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void asyncOptionsTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - final AtomicReference responseHeaders = new AtomicReference(); - - try { - final String[] expected = { "GET", "HEAD", "OPTIONS", "POST", "TRACE" }; - Future f = c.prepareOptions("http://www.apache.org/").execute(new AsyncHandlerAdapter() { - - @Override - public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - responseHeaders.set(content.getHeaders()); - return STATE.ABORT; - } - - @Override - public String onCompleted() throws Exception { - return "OK"; - } - }); - - f.get(20, TimeUnit.SECONDS) ; - FluentCaseInsensitiveStringsMap h = responseHeaders.get(); - assertNotNull(h); - String[] values = h.get("Allow").get(0).split(",|, "); - assertNotNull(values); - assertEquals(values.length, expected.length); - Arrays.sort(values); - assertEquals(values, expected); - - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void closeConnectionTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - Response r = c.prepareGet(getTargetUrl()).execute(new AsyncHandler() { - - private Response.ResponseBuilder builder = new Response.ResponseBuilder(); - - public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - builder.accumulate(content); - return STATE.CONTINUE; - } - - public void onThrowable(Throwable t) { - } - - public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { - builder.accumulate(content); - - if (content.isLast()) { - content.markUnderlyingConnectionAsToBeClosed(); - } - return STATE.CONTINUE; - } - - public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { - builder.accumulate(responseStatus); - - return STATE.CONTINUE; - } - - public Response onCompleted() throws Exception { - return builder.build(); - } - }).get(); - - assertNotNull(r); - assertEquals(r.getStatusCode(), 200); - } finally { - c.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncStreamLifecycleTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncStreamLifecycleTest.java deleted file mode 100644 index 8e702da2d3..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/AsyncStreamLifecycleTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.eclipse.jetty.continuation.Continuation; -import org.eclipse.jetty.continuation.ContinuationSupport; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.io.PrintWriter; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Tests default asynchronous life cycle. - * - * @author Hubert Iwaniuk - */ -public abstract class AsyncStreamLifecycleTest extends AbstractBasicTest { - private ExecutorService executorService = Executors.newFixedThreadPool(2); - - @AfterClass - @Override - public void tearDownGlobal() throws Exception { - super.tearDownGlobal(); - executorService.shutdownNow(); - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new AbstractHandler() { - public void handle(String s, Request request, HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException { - resp.setContentType("text/plain;charset=utf-8"); - resp.setStatus(200); - final Continuation continuation = ContinuationSupport.getContinuation(req); - continuation.suspend(); - final PrintWriter writer = resp.getWriter(); - executorService.submit(new Runnable() { - public void run() { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - logger.error("Failed to sleep for 100 ms.", e); - } - logger.info("Delivering part1."); - writer.write("part1"); - writer.flush(); - } - }); - executorService.submit(new Runnable() { - public void run() { - try { - Thread.sleep(200); - } catch (InterruptedException e) { - logger.error("Failed to sleep for 200 ms.", e); - } - logger.info("Delivering part2."); - writer.write("part2"); - writer.flush(); - continuation.complete(); - } - }); - request.setHandled(true); - } - }; - } - - // TODO Netty only. - - @Test(groups = { "standalone", "default_provider" }) - public void testStream() throws IOException { - AsyncHttpClient ahc = getAsyncHttpClient(null); - try { - final AtomicBoolean err = new AtomicBoolean(false); - final LinkedBlockingQueue queue = new LinkedBlockingQueue(); - final AtomicBoolean status = new AtomicBoolean(false); - final AtomicInteger headers = new AtomicInteger(0); - final CountDownLatch latch = new CountDownLatch(1); - ahc.executeRequest(ahc.prepareGet(getTargetUrl()).build(), new AsyncHandler() { - public void onThrowable(Throwable t) { - fail("Got throwable.", t); - err.set(true); - } - - public STATE onBodyPartReceived(HttpResponseBodyPart e) throws Exception { - if (e.length() != 0) { - String s = new String(e.getBodyPartBytes()); - logger.info("got part: {}", s); - queue.put(s); - } - return STATE.CONTINUE; - } - - public STATE onStatusReceived(HttpResponseStatus e) throws Exception { - status.set(true); - return STATE.CONTINUE; - } - - public STATE onHeadersReceived(HttpResponseHeaders e) throws Exception { - if (headers.incrementAndGet() == 2) { - throw new Exception("Analyze this."); - } - return STATE.CONTINUE; - } - - public Object onCompleted() throws Exception { - latch.countDown(); - return null; - } - }); - try { - assertTrue(latch.await(1, TimeUnit.SECONDS), "Latch failed."); - } catch (InterruptedException e) { - fail("Interrupted.", e); - } - assertFalse(err.get()); - assertEquals(queue.size(), 2); - assertTrue(queue.contains("part1")); - assertTrue(queue.contains("part2")); - assertTrue(status.get()); - assertEquals(headers.get(), 1); - } finally { - ahc.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/AuthTimeoutTest.java b/api/src/test/java/org/asynchttpclient/async/AuthTimeoutTest.java deleted file mode 100644 index 13d54d417e..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/AuthTimeoutTest.java +++ /dev/null @@ -1,236 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.ADMIN; -import static org.asynchttpclient.async.util.TestUtils.USER; -import static org.asynchttpclient.async.util.TestUtils.addBasicAuthHandler; -import static org.asynchttpclient.async.util.TestUtils.addDigestAuthHandler; -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.BoundRequestBuilder; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Response; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.util.StandardCharsets; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -public abstract class AuthTimeoutTest extends AbstractBasicTest { - - private Server server2; - - @BeforeClass(alwaysRun = true) - @Override - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - port2 = findFreePort(); - - server = newJettyHttpServer(port1); - addBasicAuthHandler(server, false, configureHandler()); - server.start(); - - server2 = newJettyHttpServer(port2); - addDigestAuthHandler(server2, true, configureHandler()); - server2.start(); - - logger.info("Local HTTP server started successfully"); - } - - @AfterClass(alwaysRun = true) - public void tearDownGlobal() throws Exception { - super.tearDownGlobal(); - server2.stop(); - } - - private class IncompleteResponseHandler extends AbstractHandler { - - public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - // NOTE: handler sends less bytes than are given in Content-Length, which should lead to timeout - - OutputStream out = response.getOutputStream(); - if (request.getHeader("X-Content") != null) { - String content = request.getHeader("X-Content"); - response.setHeader("Content-Length", String.valueOf(content.getBytes(StandardCharsets.UTF_8).length)); - out.write(content.substring(1).getBytes(StandardCharsets.UTF_8)); - } else { - response.setStatus(200); - } - out.flush(); - out.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void basicAuthTimeoutTest() throws Exception { - AsyncHttpClient client = newClient(); - try { - Future f = execute(client, server, false); - f.get(); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void basicPreemptiveAuthTimeoutTest() throws Exception { - AsyncHttpClient client = newClient(); - try { - Future f = execute(client, server, true); - f.get(); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void digestAuthTimeoutTest() throws Exception { - AsyncHttpClient client = newClient(); - try { - Future f = execute(client, server2, false); - f.get(); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void digestPreemptiveAuthTimeoutTest() throws Exception { - AsyncHttpClient client = newClient(); - try { - Future f = execute(client, server2, true); - f.get(); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void basicFutureAuthTimeoutTest() throws Exception { - AsyncHttpClient client = newClient(); - try { - Future f = execute(client, server, false); - f.get(1, TimeUnit.SECONDS); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void basicFuturePreemptiveAuthTimeoutTest() throws Exception { - AsyncHttpClient client = newClient(); - try { - Future f = execute(client, server, true); - f.get(1, TimeUnit.SECONDS); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void digestFutureAuthTimeoutTest() throws Exception { - AsyncHttpClient client = newClient(); - try { - Future f = execute(client, server2, false); - f.get(1, TimeUnit.SECONDS); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void digestFuturePreemptiveAuthTimeoutTest() throws Exception { - AsyncHttpClient client = newClient(); - try { - Future f = execute(client, server2, true); - f.get(1, TimeUnit.SECONDS); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } finally { - client.close(); - } - } - - protected void inspectException(Throwable t) { - assertNotNull(t.getCause()); - assertEquals(t.getCause().getClass(), IOException.class); - if (t.getCause() != AsyncHttpProviderUtils.REMOTELY_CLOSED_EXCEPTION) { - fail(); - } - } - - private AsyncHttpClient newClient() { - return getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setPooledConnectionIdleTimeout(2000).setConnectionTimeout(20000).setRequestTimeout(2000).build()); - } - - protected Future execute(AsyncHttpClient client, Server server, boolean preemptive) throws IOException { - BoundRequestBuilder r = client.prepareGet(getTargetUrl()).setRealm(realm(preemptive)).setHeader("X-Content", "Test"); - Future f = r.execute(); - return f; - } - - private Realm realm(boolean preemptive) { - return (new Realm.RealmBuilder()).setPrincipal(USER).setPassword(ADMIN).setUsePreemptiveAuth(preemptive).build(); - } - - @Override - protected String getTargetUrl() { - return "http://127.0.0.1/" + port1 + "/"; - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new IncompleteResponseHandler(); - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java b/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java deleted file mode 100644 index 958361c6b2..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.ADMIN; -import static org.asynchttpclient.async.util.TestUtils.SIMPLE_TEXT_FILE; -import static org.asynchttpclient.async.util.TestUtils.SIMPLE_TEXT_FILE_STRING; -import static org.asynchttpclient.async.util.TestUtils.USER; -import static org.asynchttpclient.async.util.TestUtils.addBasicAuthHandler; -import static org.asynchttpclient.async.util.TestUtils.addDigestAuthHandler; -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.BoundRequestBuilder; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Realm.AuthScheme; -import org.asynchttpclient.Response; -import org.asynchttpclient.SimpleAsyncHttpClient; -import org.asynchttpclient.consumers.AppendableBodyConsumer; -import org.asynchttpclient.generators.InputStreamBodyGenerator; -import org.asynchttpclient.util.StandardCharsets; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public abstract class BasicAuthTest extends AbstractBasicTest { - - protected static final String MY_MESSAGE = "my message"; - - private Server server2; - private Server serverNoAuth; - private int portNoAuth; - - public abstract String getProviderClass(); - - @BeforeClass(alwaysRun = true) - @Override - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - port2 = findFreePort(); - portNoAuth = findFreePort(); - - server = newJettyHttpServer(port1); - addBasicAuthHandler(server, false, configureHandler()); - server.start(); - - server2 = newJettyHttpServer(port2); - addDigestAuthHandler(server2, true, new RedirectHandler()); - server2.start(); - - // need noAuth server to verify the preemptive auth mode (see basicAuthTetPreemtiveTest) - serverNoAuth = newJettyHttpServer(portNoAuth); - serverNoAuth.setHandler(new SimpleHandler()); - serverNoAuth.start(); - - logger.info("Local HTTP server started successfully"); - } - - @AfterClass(alwaysRun = true) - public void tearDownGlobal() throws Exception { - super.tearDownGlobal(); - server2.stop(); - serverNoAuth.stop(); - } - - @Override - protected String getTargetUrl() { - return "http://127.0.0.1/" + port1 + "/"; - } - - @Override - protected String getTargetUrl2() { - return "http://127.0.0.1/" + port2 + "/uff"; - } - - protected String getTargetUrlNoAuth() { - return "http://127.0.0.1/" + portNoAuth + "/"; - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new SimpleHandler(); - } - - private static class RedirectHandler extends AbstractHandler { - - private static final Logger LOGGER = LoggerFactory.getLogger(RedirectHandler.class); - - public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - - LOGGER.info("request: " + request.getRequestURI()); - if ("/uff".equals(request.getRequestURI())) { - - LOGGER.info("redirect to /bla"); - response.setStatus(302); - response.setHeader("Location", "/bla"); - response.getOutputStream().flush(); - response.getOutputStream().close(); - - return; - - } else { - LOGGER.info("got redirected" + request.getRequestURI()); - response.addHeader("X-Auth", request.getHeader("Authorization")); - response.addHeader("X-Content-Length", String.valueOf(request.getContentLength())); - response.setStatus(200); - response.getOutputStream().write("content".getBytes(StandardCharsets.UTF_8)); - response.getOutputStream().flush(); - response.getOutputStream().close(); - } - } - } - - private static class SimpleHandler extends AbstractHandler { - - public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - - if (request.getHeader("X-401") != null) { - response.setStatus(401); - response.getOutputStream().flush(); - response.getOutputStream().close(); - - return; - } - response.addHeader("X-Auth", request.getHeader("Authorization")); - response.addHeader("X-Content-Length", String.valueOf(request.getContentLength())); - response.setStatus(200); - - int size = 10 * 1024; - if (request.getContentLength() > 0) { - size = request.getContentLength(); - } - byte[] bytes = new byte[size]; - if (bytes.length > 0) { - int read = request.getInputStream().read(bytes); - if (read > 0) { - response.getOutputStream().write(bytes, 0, read); - } - } - response.getOutputStream().flush(); - response.getOutputStream().close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthTest() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.prepareGet(getTargetUrl())// - .setRealm((new Realm.RealmBuilder()).setPrincipal(USER).setPassword(ADMIN).build())// - .execute(); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertNotNull(resp.getHeader("X-Auth")); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void redirectAndBasicAuthTest() throws Exception, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).setMaxRedirects(10).build()); - try { - Future f = client.prepareGet(getTargetUrl2())// - .setRealm((new Realm.RealmBuilder()).setPrincipal(USER).setPassword(ADMIN).build())// - .execute(); - Response resp = f.get(3, TimeUnit.SECONDS); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertNotNull(resp); - assertNotNull(resp.getHeader("X-Auth")); - - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basic401Test() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - BoundRequestBuilder r = client.prepareGet(getTargetUrl())// - .setHeader("X-401", "401")// - .setRealm((new Realm.RealmBuilder()).setPrincipal(USER).setPassword(ADMIN).build()); - - Future f = r.execute(new AsyncHandler() { - - private HttpResponseStatus status; - - public void onThrowable(Throwable t) { - - } - - public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - return STATE.CONTINUE; - } - - public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { - this.status = responseStatus; - - if (status.getStatusCode() != 200) { - return STATE.ABORT; - } - return STATE.CONTINUE; - } - - public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { - return STATE.CONTINUE; - } - - public Integer onCompleted() throws Exception { - return status.getStatusCode(); - } - }); - Integer statusCode = f.get(10, TimeUnit.SECONDS); - assertNotNull(statusCode); - assertEquals(statusCode.intValue(), 401); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthTestPreemtiveTest() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - // send the request to the no-auth endpoint to be able to verify the auth header is - // really sent preemptively for the initial call. - Future f = client.prepareGet(getTargetUrlNoAuth())// - .setRealm((new Realm.RealmBuilder()).setScheme(AuthScheme.BASIC).setPrincipal(USER).setPassword(ADMIN).setUsePreemptiveAuth(true).build())// - .execute(); - - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertNotNull(resp.getHeader("X-Auth")); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthNegativeTest() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.prepareGet(getTargetUrl())// - .setRealm((new Realm.RealmBuilder()).setPrincipal("fake").setPassword(ADMIN).build())// - .execute(); - - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), 401); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthInputStreamTest() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.preparePost(getTargetUrl())// - .setBody(new ByteArrayInputStream("test".getBytes()))// - .setRealm((new Realm.RealmBuilder()).setPrincipal(USER).setPassword(ADMIN).build())// - .execute(); - - Response resp = f.get(30, TimeUnit.SECONDS); - assertNotNull(resp); - assertNotNull(resp.getHeader("X-Auth")); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getResponseBody(), "test"); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthFileTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.preparePost(getTargetUrl())// - .setBody(SIMPLE_TEXT_FILE)// - .setRealm((new Realm.RealmBuilder()).setPrincipal(USER).setPassword(ADMIN).build())// - .execute(); - - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertNotNull(resp.getHeader("X-Auth")); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthAsyncConfigTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRealm((new Realm.RealmBuilder()).setPrincipal(USER).setPassword(ADMIN).build()).build()); - try { - Future f = client.preparePost(getTargetUrl())// - .setBody(SIMPLE_TEXT_FILE_STRING)// - .execute(); - - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertNotNull(resp.getHeader("X-Auth")); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthFileNoKeepAliveTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(false).build()); - try { - - Future f = client.preparePost(getTargetUrl())// - .setBody(SIMPLE_TEXT_FILE)// - .setRealm((new Realm.RealmBuilder()).setPrincipal(USER).setPassword(ADMIN).build())// - .execute(); - - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertNotNull(resp.getHeader("X-Auth")); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void stringBuilderBodyConsumerTest() throws Exception { - - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setRealmPrincipal(USER).setRealmPassword(ADMIN) - .setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); - try { - StringBuilder s = new StringBuilder(); - Future future = client.post(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes())), new AppendableBodyConsumer(s)); - - Response response = future.get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(s.toString(), MY_MESSAGE); - assertEquals(response.getStatusCode(), HttpServletResponse.SC_OK); - assertNotNull(response.getHeader("X-Auth")); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void noneAuthTest() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - BoundRequestBuilder r = client.prepareGet(getTargetUrl()).setRealm((new Realm.RealmBuilder()).setPrincipal(USER).setPassword(ADMIN).build()); - - Future f = r.execute(); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertNotNull(resp.getHeader("X-Auth")); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java b/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java deleted file mode 100644 index 9f13a7b51b..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.SIMPLE_TEXT_FILE; -import static org.asynchttpclient.async.util.TestUtils.SIMPLE_TEXT_FILE_STRING; -import static org.asynchttpclient.async.util.TestUtils.createSSLContext; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig.Builder; -import org.asynchttpclient.Response; -import org.testng.Assert; -import org.testng.annotations.Test; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLSession; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.net.ConnectException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -public abstract class BasicHttpsTest extends AbstractBasicHttpsTest { - - protected String getTargetUrl() { - return String.format("https://127.0.0.1:%d/foo/test", port1); - } - - @Test(groups = { "standalone", "default_provider" }) - public void zeroCopyPostTest() throws Exception { - - final AsyncHttpClient client = getAsyncHttpClient(new Builder().setSSLContext(createSSLContext(new AtomicBoolean(true))).build()); - try { - Response resp = client.preparePost(getTargetUrl()).setBody(SIMPLE_TEXT_FILE).setHeader("Content-Type", "text/html").execute().get(); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void multipleSSLRequestsTest() throws Exception { - final AsyncHttpClient c = getAsyncHttpClient(new Builder().setSSLContext(createSSLContext(new AtomicBoolean(true))).build()); - try { - String body = "hello there"; - - // once - Response response = c.preparePost(getTargetUrl()).setBody(body).setHeader("Content-Type", "text/html").execute().get(TIMEOUT, TimeUnit.SECONDS); - - assertEquals(response.getResponseBody(), body); - - // twice - response = c.preparePost(getTargetUrl()).setBody(body).setHeader("Content-Type", "text/html").execute().get(TIMEOUT, TimeUnit.SECONDS); - - assertEquals(response.getResponseBody(), body); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void multipleSSLWithoutCacheTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new Builder().setSSLContext(createSSLContext(new AtomicBoolean(true))).setAllowPoolingSslConnections(false).build()); - try { - String body = "hello there"; - c.preparePost(getTargetUrl()).setBody(body).setHeader("Content-Type", "text/html").execute(); - - c.preparePost(getTargetUrl()).setBody(body).setHeader("Content-Type", "text/html").execute(); - - Response response = c.preparePost(getTargetUrl()).setBody(body).setHeader("Content-Type", "text/html").execute().get(); - - assertEquals(response.getResponseBody(), body); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void reconnectsAfterFailedCertificationPath() throws Exception { - AtomicBoolean trusted = new AtomicBoolean(false); - AsyncHttpClient c = getAsyncHttpClient(new Builder().setSSLContext(createSSLContext(trusted)).build()); - try { - String body = "hello there"; - - // first request fails because server certificate is rejected - Throwable cause = null; - try { - c.preparePost(getTargetUrl()).setBody(body).setHeader("Content-Type", "text/html").execute().get(TIMEOUT, TimeUnit.SECONDS); - } catch (final ExecutionException e) { - cause = e.getCause(); - if (cause instanceof ConnectException) { - //assertNotNull(cause.getCause()); - assertTrue(cause.getCause() instanceof SSLHandshakeException, "Expected an SSLHandshakeException, got a " + cause.getCause()); - } else { - assertTrue(cause instanceof IOException, "Expected an IOException, got a " + cause); - } - } - assertNotNull(cause); - - trusted.set(true); - - // second request should succeed - Response response = c.preparePost(getTargetUrl()).setBody(body).setHeader("Content-Type", "text/html").execute().get(TIMEOUT, TimeUnit.SECONDS); - - assertEquals(response.getResponseBody(), body); - } finally { - c.close(); - } - } - - @Test(timeOut = 5000) - public void failInstantlyIfHostNamesDiffer() throws Exception { - AsyncHttpClient client = null; - - try { - final Builder builder = new Builder().setHostnameVerifier(new HostnameVerifier() { - - public boolean verify(String arg0, SSLSession arg1) { - return false; - } - }).setRequestTimeout(20000); - - client = getAsyncHttpClient(builder.build()); - - try { - client.prepareGet("https://github.com/AsyncHttpClient/async-http-client/issues/355").execute().get(TIMEOUT, TimeUnit.SECONDS); - - Assert.assertTrue(false, "Shouldn't be here: should get an Exception"); - } catch (ExecutionException e) { - Assert.assertTrue(e.getCause() instanceof ConnectException, "Cause should be a ConnectException"); - } catch (Exception e) { - Assert.assertTrue(false, "Shouldn't be here: should get a ConnectException wrapping a ConnectException"); - } - - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/BodyChunkTest.java b/api/src/test/java/org/asynchttpclient/async/BodyChunkTest.java deleted file mode 100644 index 727e0d67a0..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/BodyChunkTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.generators.InputStreamBodyGenerator; -import org.testng.annotations.Test; - -import java.io.ByteArrayInputStream; -import java.util.concurrent.Future; - -public abstract class BodyChunkTest extends AbstractBasicTest { - - private static final String MY_MESSAGE = "my message"; - - @Test(groups = { "standalone", "default_provider" }) - public void negativeContentTypeTest() throws Exception { - - AsyncHttpClientConfig.Builder confbuilder = new AsyncHttpClientConfig.Builder(); - confbuilder = confbuilder.setConnectionTimeout(100); - confbuilder = confbuilder.setMaxConnections(50); - confbuilder = confbuilder.setRequestTimeout(5 * 60 * 1000); // 5 minutes - - // Create client - AsyncHttpClient client = getAsyncHttpClient(confbuilder.build()); - try { - - RequestBuilder requestBuilder = new RequestBuilder("POST").setUrl(getTargetUrl()).setHeader("Content-Type", "message/rfc822"); - - requestBuilder.setBody(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes()))); - - Future future = client.executeRequest(requestBuilder.build()); - - System.out.println("waiting for response"); - Response response = future.get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getResponseBody(), MY_MESSAGE); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/BodyDeferringAsyncHandlerTest.java b/api/src/test/java/org/asynchttpclient/async/BodyDeferringAsyncHandlerTest.java deleted file mode 100644 index baf91d11e2..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/BodyDeferringAsyncHandlerTest.java +++ /dev/null @@ -1,268 +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.async; - -import static org.apache.commons.io.IOUtils.copy; -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.BodyDeferringAsyncHandler; -import org.asynchttpclient.BodyDeferringAsyncHandler.BodyDeferringInputStream; -import org.asynchttpclient.BoundRequestBuilder; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeoutException; - -public abstract class BodyDeferringAsyncHandlerTest extends AbstractBasicTest { - - // not a half gig ;) for test shorter run's sake - protected static final int HALF_GIG = 100000; - - public static class SlowAndBigHandler extends AbstractHandler { - - public void handle(String pathInContext, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { - - httpResponse.setStatus(200); - httpResponse.setContentLength(HALF_GIG); - httpResponse.setContentType("application/octet-stream"); - - httpResponse.flushBuffer(); - - final boolean wantFailure = httpRequest.getHeader("X-FAIL-TRANSFER") != null; - final boolean wantSlow = httpRequest.getHeader("X-SLOW") != null; - - OutputStream os = httpResponse.getOutputStream(); - for (int i = 0; i < HALF_GIG; i++) { - os.write(i % 255); - - if (wantSlow) { - try { - Thread.sleep(300); - } catch (InterruptedException ex) { - // nuku - } - } - - if (wantFailure) { - if (i > HALF_GIG / 2) { - // kaboom - // yes, response is commited, but Jetty does aborts and - // drops connection - httpResponse.sendError(500); - break; - } - } - } - - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - } - } - - // a /dev/null but counting how many bytes it ditched - public static class CountingOutputStream extends OutputStream { - private int byteCount = 0; - - @Override - public void write(int b) throws IOException { - // /dev/null - byteCount++; - } - - public int getByteCount() { - return byteCount; - } - } - - public AbstractHandler configureHandler() throws Exception { - return new SlowAndBigHandler(); - } - - public AsyncHttpClientConfig getAsyncHttpClientConfig() { - // for this test brevity's sake, we are limiting to 1 retries - return new AsyncHttpClientConfig.Builder().setMaxRequestRetry(0).setRequestTimeout(10000).build(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void deferredSimple() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(getAsyncHttpClientConfig()); - try { - BoundRequestBuilder r = client.prepareGet("http://127.0.0.1/" + port1 + "/deferredSimple"); - - CountingOutputStream cos = new CountingOutputStream(); - BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(cos); - Future f = r.execute(bdah); - Response resp = bdah.getResponse(); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("content-length"), String.valueOf(HALF_GIG)); - // we got headers only, it's probably not all yet here (we have BIG file - // downloading) - assertTrue(cos.getByteCount() <= HALF_GIG); - - // now be polite and wait for body arrival too (otherwise we would be - // dropping the "line" on server) - f.get(); - // it all should be here now - assertEquals(cos.getByteCount(), HALF_GIG); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void deferredSimpleWithFailure() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(getAsyncHttpClientConfig()); - try { - BoundRequestBuilder r = client.prepareGet("http://127.0.0.1/" + port1 + "/deferredSimpleWithFailure").addHeader("X-FAIL-TRANSFER", - Boolean.TRUE.toString()); - - CountingOutputStream cos = new CountingOutputStream(); - BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(cos); - Future f = r.execute(bdah); - Response resp = bdah.getResponse(); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("content-length"), String.valueOf(HALF_GIG)); - // we got headers only, it's probably not all yet here (we have BIG file - // downloading) - assertTrue(cos.getByteCount() <= HALF_GIG); - - // now be polite and wait for body arrival too (otherwise we would be - // dropping the "line" on server) - try { - f.get(); - fail("get() should fail with IOException!"); - } catch (Exception e) { - // good - } - // it's incomplete, there was an error - assertNotEquals(cos.getByteCount(), HALF_GIG); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void deferredInputStreamTrick() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(getAsyncHttpClientConfig()); - try { - BoundRequestBuilder r = client.prepareGet("http://127.0.0.1/" + port1 + "/deferredInputStreamTrick"); - - PipedOutputStream pos = new PipedOutputStream(); - PipedInputStream pis = new PipedInputStream(pos); - BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(pos); - - Future f = r.execute(bdah); - - BodyDeferringInputStream is = new BodyDeferringInputStream(f, bdah, pis); - - Response resp = is.getAsapResponse(); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("content-length"), String.valueOf(HALF_GIG)); - // "consume" the body, but our code needs input stream - CountingOutputStream cos = new CountingOutputStream(); - try { - copy(is, cos); - } finally { - is.close(); - cos.close(); - } - - // now we don't need to be polite, since consuming and closing - // BodyDeferringInputStream does all. - // it all should be here now - assertEquals(cos.getByteCount(), HALF_GIG); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void deferredInputStreamTrickWithFailure() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(getAsyncHttpClientConfig()); - - try { - BoundRequestBuilder r = client.prepareGet("http://127.0.0.1/" + port1 + "/deferredInputStreamTrickWithFailure").addHeader("X-FAIL-TRANSFER", - Boolean.TRUE.toString()); - PipedOutputStream pos = new PipedOutputStream(); - PipedInputStream pis = new PipedInputStream(pos); - BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(pos); - - Future f = r.execute(bdah); - - BodyDeferringInputStream is = new BodyDeferringInputStream(f, bdah, pis); - - Response resp = is.getAsapResponse(); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("content-length"), String.valueOf(HALF_GIG)); - // "consume" the body, but our code needs input stream - CountingOutputStream cos = new CountingOutputStream(); - try { - try { - copy(is, cos); - } finally { - is.close(); - cos.close(); - } - fail("InputStream consumption should fail with IOException!"); - } catch (IOException e) { - // good! - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testConnectionRefused() throws IOException, ExecutionException, TimeoutException, InterruptedException { - int newPortWithoutAnyoneListening = findFreePort(); - AsyncHttpClient client = getAsyncHttpClient(getAsyncHttpClientConfig()); - try { - BoundRequestBuilder r = client.prepareGet("http://127.0.0.1/" + newPortWithoutAnyoneListening + "/testConnectionRefused"); - - CountingOutputStream cos = new CountingOutputStream(); - BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(cos); - r.execute(bdah); - try { - bdah.getResponse(); - fail("IOException should be thrown here!"); - } catch (IOException e) { - // good - } - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/ByteBufferCapacityTest.java b/api/src/test/java/org/asynchttpclient/async/ByteBufferCapacityTest.java deleted file mode 100644 index 7223357bac..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/ByteBufferCapacityTest.java +++ /dev/null @@ -1,109 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.createTempFile; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Enumeration; -import java.util.concurrent.atomic.AtomicInteger; - -public abstract class ByteBufferCapacityTest extends AbstractBasicTest { - - private class BasicHandler extends AbstractHandler { - - public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { - - Enumeration e = httpRequest.getHeaderNames(); - String param; - while (e.hasMoreElements()) { - param = e.nextElement().toString(); - httpResponse.addHeader("X-" + param, httpRequest.getHeader(param)); - } - - int size = 10 * 1024; - if (httpRequest.getContentLength() > 0) { - size = httpRequest.getContentLength(); - } - byte[] bytes = new byte[size]; - if (bytes.length > 0) { - final InputStream in = httpRequest.getInputStream(); - final OutputStream out = httpResponse.getOutputStream(); - int read; - while ((read = in.read(bytes)) != -1) { - out.write(bytes, 0, read); - } - } - - httpResponse.setStatus(200); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new BasicHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicByteBufferTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - File largeFile = createTempFile(1024 * 100 * 10); - final AtomicInteger byteReceived = new AtomicInteger(); - - try { - Response response = c.preparePut(getTargetUrl()).setBody(largeFile).execute(new AsyncCompletionHandlerAdapter() { - @Override - public STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { - byteReceived.addAndGet(content.getBodyByteBuffer().capacity()); - return super.onBodyPartReceived(content); - } - - }).get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(byteReceived.get(), largeFile.length()); - assertEquals(response.getResponseBody().length(), largeFile.length()); - - } catch (IOException ex) { - fail("Should have timed out"); - } - } finally { - c.close(); - } - } - - public String getTargetUrl() { - return String.format("http://127.0.0.1:%d/foo/test", port1); - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java b/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java deleted file mode 100644 index 0566ecdbaa..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java +++ /dev/null @@ -1,83 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.LARGE_IMAGE_BYTES; -import static org.asynchttpclient.async.util.TestUtils.LARGE_IMAGE_FILE; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; -import static org.testng.FileAssert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.generators.InputStreamBodyGenerator; -import org.testng.annotations.Test; - -import java.io.BufferedInputStream; -import java.io.FileInputStream; - -/** - * Test that the url fetcher is able to communicate via a proxy - * - * @author dominict - */ -abstract public class ChunkingTest extends AbstractBasicTest { - // So we can just test the returned data is the image, - // and doesn't contain the chunked delimeters. - - /** - * Tests that the custom chunked stream result in success and content returned that is unchunked - */ - @Test() - public void testCustomChunking() throws Exception { - AsyncHttpClientConfig.Builder bc = new AsyncHttpClientConfig.Builder(); - - bc.setAllowPoolingConnections(true); - bc.setMaxConnectionsPerHost(1); - bc.setMaxConnections(1); - bc.setConnectionTimeout(1000); - bc.setRequestTimeout(1000); - bc.setFollowRedirect(true); - - AsyncHttpClient c = getAsyncHttpClient(bc.build()); - try { - - RequestBuilder builder = new RequestBuilder("POST"); - builder.setUrl(getTargetUrl()); - // made buff in stream big enough to mark. - builder.setBody(new InputStreamBodyGenerator(new BufferedInputStream(new FileInputStream(LARGE_IMAGE_FILE), 400000))); - - Request r = builder.build(); - - Response response = c.executeRequest(r).get(); - if (500 == response.getStatusCode()) { - StringBuilder sb = new StringBuilder(); - sb.append("==============\n"); - sb.append("500 response from call\n"); - sb.append("Headers:" + response.getHeaders() + "\n"); - sb.append("==============\n"); - logger.debug(sb.toString()); - assertEquals(response.getStatusCode(), 500, "Should have 500 status code"); - assertTrue(response.getHeader("X-Exception").contains("invalid.chunk.length"), "Should have failed due to chunking"); - fail("HARD Failing the test due to provided InputStreamBodyGenerator, chunking incorrectly:" + response.getHeader("X-Exception")); - } else { - assertEquals(response.getResponseBodyAsBytes(), LARGE_IMAGE_BYTES); - } - } finally { - c.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/ComplexClientTest.java b/api/src/test/java/org/asynchttpclient/async/ComplexClientTest.java deleted file mode 100644 index 7ecc68efb6..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/ComplexClientTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.Response; -import org.testng.annotations.Test; - -import java.util.concurrent.TimeUnit; - -public abstract class ComplexClientTest extends AbstractBasicTest { - - @Test(groups = { "standalone", "default_provider" }) - public void multipleRequestsTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - String body = "hello there"; - - // once - Response response = c.preparePost(getTargetUrl()).setBody(body).setHeader("Content-Type", "text/html").execute().get(TIMEOUT, TimeUnit.SECONDS); - - assertEquals(response.getResponseBody(), body); - - // twice - response = c.preparePost(getTargetUrl()).setBody(body).setHeader("Content-Type", "text/html").execute().get(TIMEOUT, TimeUnit.SECONDS); - - assertEquals(response.getResponseBody(), body); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void urlWithoutSlashTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - String body = "hello there"; - - Response response = c.preparePost(String.format("http://127.0.0.1:%d/foo/test", port1)).setBody(body).setHeader("Content-Type", "text/html").execute().get(TIMEOUT, TimeUnit.SECONDS); - - assertEquals(response.getResponseBody(), body); - } finally { - c.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java b/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java deleted file mode 100644 index 86ea33b6dc..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncCompletionHandlerBase; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -public abstract class ConnectionPoolTest extends AbstractBasicTest { - protected final Logger log = LoggerFactory.getLogger(AbstractBasicTest.class); - - @Test(groups = { "standalone", "default_provider" }) - public void testMaxTotalConnections() { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setMaxConnections(1).build()); - try { - String url = getTargetUrl(); - int i; - Exception exception = null; - for (i = 0; i < 3; i++) { - try { - log.info("{} requesting url [{}]...", i, url); - Response response = client.prepareGet(url).execute().get(); - log.info("{} response [{}].", i, response); - } catch (Exception ex) { - exception = ex; - } - } - assertNull(exception); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testMaxTotalConnectionsException() { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setMaxConnections(1).build()); - try { - String url = getTargetUrl(); - int i; - Exception exception = null; - for (i = 0; i < 20; i++) { - try { - log.info("{} requesting url [{}]...", i, url); - - if (i < 5) { - client.prepareGet(url).execute().get(); - } else { - client.prepareGet(url).execute(); - } - } catch (Exception ex) { - exception = ex; - break; - } - } - assertNotNull(exception); - assertNotNull(exception.getMessage()); - assertEquals(exception.getMessage(), "Too many connections 1"); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }, enabled = true, invocationCount = 10, alwaysRun = true) - public void asyncDoGetKeepAliveHandlerTest_channelClosedDoesNotFail() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - // Use a l in case the assert fail - final CountDownLatch l = new CountDownLatch(2); - - final Map remoteAddresses = new ConcurrentHashMap(); - - AsyncCompletionHandler handler = new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - System.out.println("ON COMPLETED INVOKED " + response.getHeader("X-KEEP-ALIVE")); - try { - assertEquals(response.getStatusCode(), 200); - remoteAddresses.put(response.getHeader("X-KEEP-ALIVE"), true); - } finally { - l.countDown(); - } - return response; - } - }; - - client.prepareGet(getTargetUrl()).execute(handler).get(); - server.stop(); - server.start(); - client.prepareGet(getTargetUrl()).execute(handler); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timed out"); - } - - assertEquals(remoteAddresses.size(), 2); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void multipleMaxConnectionOpenTest() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setConnectionTimeout(5000).setMaxConnections(1).build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - try { - String body = "hello there"; - - // once - Response response = c.preparePost(getTargetUrl()).setBody(body).execute().get(TIMEOUT, TimeUnit.SECONDS); - - assertEquals(response.getResponseBody(), body); - - // twice - Exception exception = null; - try { - c.preparePost(String.format("http://127.0.0.1:%d/foo/test", port2)).setBody(body).execute().get(TIMEOUT, TimeUnit.SECONDS); - fail("Should throw exception. Too many connections issued."); - } catch (Exception ex) { - ex.printStackTrace(); - exception = ex; - } - assertNotNull(exception); - assertEquals(exception.getMessage(), "Too many connections 1"); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void multipleMaxConnectionOpenTestWithQuery() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setConnectionTimeout(5000).setMaxConnections(1).build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - try { - String body = "hello there"; - - // once - Response response = c.preparePost(getTargetUrl() + "?foo=bar").setBody(body).execute().get(TIMEOUT, TimeUnit.SECONDS); - - assertEquals(response.getResponseBody(), "foo_" + body); - - // twice - Exception exception = null; - try { - response = c.preparePost(getTargetUrl()).setBody(body).execute().get(TIMEOUT, TimeUnit.SECONDS); - } catch (Exception ex) { - ex.printStackTrace(); - exception = ex; - } - assertNull(exception); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - } finally { - c.close(); - } - } - - /** - * This test just make sure the hack used to catch disconnected channel under win7 doesn't throw any exception. The onComplete method must be only called once. - * - * @throws Exception - * if something wrong happens. - */ - @Test(groups = { "standalone", "default_provider" }) - public void win7DisconnectTest() throws Exception { - final AtomicInteger count = new AtomicInteger(0); - - AsyncHttpClient client = getAsyncHttpClient(null); - try { - AsyncCompletionHandler handler = new AsyncCompletionHandlerAdapter() { - - @Override - public Response onCompleted(Response response) throws Exception { - - count.incrementAndGet(); - StackTraceElement e = new StackTraceElement("sun.nio.ch.SocketDispatcher", "read0", null, -1); - IOException t = new IOException(); - t.setStackTrace(new StackTraceElement[] { e }); - throw t; - } - }; - - try { - client.prepareGet(getTargetUrl()).execute(handler).get(); - fail("Must have received an exception"); - } catch (ExecutionException ex) { - assertNotNull(ex); - assertNotNull(ex.getCause()); - assertEquals(ex.getCause().getClass(), IOException.class); - assertEquals(count.get(), 1); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncHandlerOnThrowableTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final AtomicInteger count = new AtomicInteger(); - final String THIS_IS_NOT_FOR_YOU = "This is not for you"; - final CountDownLatch latch = new CountDownLatch(16); - for (int i = 0; i < 16; i++) { - client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerBase() { - @Override - public Response onCompleted(Response response) throws Exception { - throw new Exception(THIS_IS_NOT_FOR_YOU); - } - }); - - client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerBase() { - @Override - public void onThrowable(Throwable t) { - if (t.getMessage() != null && t.getMessage().equalsIgnoreCase(THIS_IS_NOT_FOR_YOU)) { - count.incrementAndGet(); - } - } - - @Override - public Response onCompleted(Response response) throws Exception { - latch.countDown(); - return response; - } - }); - } - latch.await(TIMEOUT, TimeUnit.SECONDS); - assertEquals(count.get(), 0); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/DigestAuthTest.java b/api/src/test/java/org/asynchttpclient/async/DigestAuthTest.java deleted file mode 100644 index 2753c60625..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/DigestAuthTest.java +++ /dev/null @@ -1,115 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.ADMIN; -import static org.asynchttpclient.async.util.TestUtils.USER; -import static org.asynchttpclient.async.util.TestUtils.addDigestAuthHandler; -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public abstract class DigestAuthTest extends AbstractBasicTest { - - @BeforeClass(alwaysRun = true) - @Override - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - - server = newJettyHttpServer(port1); - addDigestAuthHandler(server, false, configureHandler()); - server.start(); - logger.info("Local HTTP server started successfully"); - } - - private static class SimpleHandler extends AbstractHandler { - public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - - response.addHeader("X-Auth", request.getHeader("Authorization")); - response.setStatus(200); - response.getOutputStream().flush(); - response.getOutputStream().close(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new SimpleHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void digestAuthTest() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.prepareGet("http://127.0.0.1/" + port1 + "/")// - .setRealm(new Realm.RealmBuilder().setPrincipal(USER).setPassword(ADMIN).setRealmName("MyRealm").setScheme(Realm.AuthScheme.DIGEST).build())// - .execute(); - Response resp = f.get(60, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertNotNull(resp.getHeader("X-Auth")); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void digestAuthTestWithoutScheme() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.prepareGet("http://127.0.0.1/" + port1 + "/")// - .setRealm(new Realm.RealmBuilder().setPrincipal(USER).setPassword(ADMIN).setRealmName("MyRealm").build())// - .execute(); - Response resp = f.get(60, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertNotNull(resp.getHeader("X-Auth")); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void digestAuthNegativeTest() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.prepareGet("http://127.0.0.1/" + port1 + "/")// - .setRealm(new Realm.RealmBuilder().setPrincipal("fake").setPassword(ADMIN).setScheme(Realm.AuthScheme.DIGEST).build())// - .execute(); - Response resp = f.get(20, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), 401); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/EmptyBodyTest.java b/api/src/test/java/org/asynchttpclient/async/EmptyBodyTest.java deleted file mode 100644 index fc0693cba4..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/EmptyBodyTest.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.io.InputStream; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Tests case where response doesn't have body. - * - * @author Hubert Iwaniuk - */ -public abstract class EmptyBodyTest extends AbstractBasicTest { - private class NoBodyResponseHandler extends AbstractHandler { - public void handle(String s, Request request, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - - if (!req.getMethod().equalsIgnoreCase("PUT")) { - resp.setStatus(HttpServletResponse.SC_OK); - } else { - resp.setStatus(204); - } - request.setHandled(true); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new NoBodyResponseHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void testEmptyBody() throws IOException { - AsyncHttpClient ahc = getAsyncHttpClient(null); - try { - final AtomicBoolean err = new AtomicBoolean(false); - final LinkedBlockingQueue queue = new LinkedBlockingQueue(); - final AtomicBoolean status = new AtomicBoolean(false); - final AtomicInteger headers = new AtomicInteger(0); - final CountDownLatch latch = new CountDownLatch(1); - ahc.executeRequest(ahc.prepareGet(getTargetUrl()).build(), new AsyncHandler() { - public void onThrowable(Throwable t) { - fail("Got throwable.", t); - err.set(true); - } - - public STATE onBodyPartReceived(HttpResponseBodyPart e) throws Exception { - byte[] bytes = e.getBodyPartBytes(); - - if (bytes.length != 0) { - String s = new String(bytes); - logger.info("got part: {}", s); - logger.warn("Sampling stacktrace.", new Throwable("trace that, we should not get called for empty body.")); - queue.put(s); - } - return STATE.CONTINUE; - } - - public STATE onStatusReceived(HttpResponseStatus e) throws Exception { - status.set(true); - return AsyncHandler.STATE.CONTINUE; - } - - public STATE onHeadersReceived(HttpResponseHeaders e) throws Exception { - if (headers.incrementAndGet() == 2) { - throw new Exception("Analyze this."); - } - return STATE.CONTINUE; - } - - public Object onCompleted() throws Exception { - latch.countDown(); - return null; - } - }); - try { - assertTrue(latch.await(1, TimeUnit.SECONDS), "Latch failed."); - } catch (InterruptedException e) { - fail("Interrupted.", e); - } - assertFalse(err.get()); - assertEquals(queue.size(), 0); - assertTrue(status.get()); - assertEquals(headers.get(), 1); - } finally { - ahc.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testPutEmptyBody() throws Exception { - AsyncHttpClient ahc = getAsyncHttpClient(null); - try { - Response response = ahc.preparePut(getTargetUrl()).setBody("String").execute().get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 204); - assertEquals(response.getResponseBody(), ""); - assertTrue(response.getResponseBodyAsStream() instanceof InputStream); - assertEquals(response.getResponseBodyAsStream().read(), -1); - } finally { - ahc.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/ErrorResponseTest.java b/api/src/test/java/org/asynchttpclient/async/ErrorResponseTest.java deleted file mode 100644 index 4759564cf7..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/ErrorResponseTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.Response; -import org.asynchttpclient.util.StandardCharsets; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -/** - * Tests to reproduce issues with handling of error responses - * - * @author Tatu Saloranta - */ -public abstract class ErrorResponseTest extends AbstractBasicTest { - final static String BAD_REQUEST_STR = "Very Bad Request! No cookies."; - - private static class ErrorHandler extends AbstractHandler { - public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - try { - Thread.sleep(210L); - } catch (InterruptedException e) { - } - response.setContentType("text/plain"); - response.setStatus(400); - OutputStream out = response.getOutputStream(); - out.write(BAD_REQUEST_STR.getBytes(StandardCharsets.UTF_8)); - out.flush(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new ErrorHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void testQueryParameters() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.prepareGet("http://127.0.0.1/" + port1 + "/foo").addHeader("Accepts", "*/*").execute(); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), 400); - assertEquals(resp.getResponseBody(), BAD_REQUEST_STR); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/Expect100ContinueTest.java b/api/src/test/java/org/asynchttpclient/async/Expect100ContinueTest.java deleted file mode 100644 index cb9694a819..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/Expect100ContinueTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.SIMPLE_TEXT_FILE; -import static org.asynchttpclient.async.util.TestUtils.SIMPLE_TEXT_FILE_STRING; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.concurrent.Future; - -/** - * Test the Expect: 100-Continue. - */ -public abstract class Expect100ContinueTest extends AbstractBasicTest { - - private static class ZeroCopyHandler extends AbstractHandler { - public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { - - int size = 10 * 1024; - if (httpRequest.getContentLength() > 0) { - size = httpRequest.getContentLength(); - } - byte[] bytes = new byte[size]; - if (bytes.length > 0) { - final int read = httpRequest.getInputStream().read(bytes); - httpResponse.getOutputStream().write(bytes, 0, read); - } - - httpResponse.setStatus(200); - httpResponse.getOutputStream().flush(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new ZeroCopyHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void Expect100Continue() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.preparePut("http://127.0.0.1/" + port1 + "/").setHeader("Expect", "100-continue").setBody(SIMPLE_TEXT_FILE).execute(); - Response resp = f.get(); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/FastUnauthorizedUploadTest.java b/api/src/test/java/org/asynchttpclient/async/FastUnauthorizedUploadTest.java deleted file mode 100644 index 59ab429241..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/FastUnauthorizedUploadTest.java +++ /dev/null @@ -1,63 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.createTempFile; -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.Response; -import org.asynchttpclient.multipart.FilePart; -import org.asynchttpclient.util.StandardCharsets; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.File; -import java.io.IOException; - -public abstract class FastUnauthorizedUploadTest extends AbstractBasicTest { - - @Override - public AbstractHandler configureHandler() throws Exception { - return new AbstractHandler() { - - public void handle(String target, Request baseRequest, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - - resp.setStatus(401); - resp.getOutputStream().flush(); - resp.getOutputStream().close(); - - baseRequest.setHandled(true); - } - }; - } - - @Test(groups = { "standalone", "default_provider" }, enabled = true) - public void testUnauthorizedWhileUploading() throws Exception { - File file = createTempFile(1024 * 1024); - - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", file, "application/octet-stream", StandardCharsets.UTF_8)).execute() - .get(); - assertEquals(response.getStatusCode(), 401); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/FilePartLargeFileTest.java b/api/src/test/java/org/asynchttpclient/async/FilePartLargeFileTest.java deleted file mode 100644 index b2f378f9c7..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/FilePartLargeFileTest.java +++ /dev/null @@ -1,86 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.LARGE_IMAGE_FILE; -import static org.asynchttpclient.async.util.TestUtils.createTempFile; -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.asynchttpclient.multipart.FilePart; -import org.asynchttpclient.util.StandardCharsets; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.File; -import java.io.IOException; - -public abstract class FilePartLargeFileTest extends AbstractBasicTest { - - @Override - public AbstractHandler configureHandler() throws Exception { - return new AbstractHandler() { - - public void handle(String target, Request baseRequest, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - - ServletInputStream in = req.getInputStream(); - byte[] b = new byte[8192]; - - int count = -1; - int total = 0; - while ((count = in.read(b)) != -1) { - b = new byte[8192]; - total += count; - } - resp.setStatus(200); - resp.addHeader("X-TRANFERED", String.valueOf(total)); - resp.getOutputStream().flush(); - resp.getOutputStream().close(); - - baseRequest.setHandled(true); - } - }; - } - - @Test(groups = { "standalone", "default_provider" }, enabled = true) - public void testPutImageFile() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(100 * 6000).build()); - try { - Response response = client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", LARGE_IMAGE_FILE, "application/octet-stream", StandardCharsets.UTF_8)).execute().get(); - assertEquals(response.getStatusCode(), 200); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = true) - public void testPutLargeTextFile() throws Exception { - File file = createTempFile(1024 * 1024); - - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", file, "application/octet-stream", StandardCharsets.UTF_8)).execute().get(); - assertEquals(response.getStatusCode(), 200); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/FilterTest.java b/api/src/test/java/org/asynchttpclient/async/FilterTest.java deleted file mode 100644 index 58f4c44d93..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/FilterTest.java +++ /dev/null @@ -1,245 +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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.extra.ThrottleRequestFilter; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.ResponseFilter; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; - -public abstract class FilterTest extends AbstractBasicTest { - - private static class BasicHandler extends AbstractHandler { - - public void handle(String s, org.eclipse.jetty.server.Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { - - Enumeration e = httpRequest.getHeaderNames(); - String param; - while (e.hasMoreElements()) { - param = e.nextElement().toString(); - httpResponse.addHeader(param, httpRequest.getHeader(param)); - } - - httpResponse.setStatus(200); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new BasicHandler(); - } - - public String getTargetUrl() { - return String.format("http://127.0.0.1:%d/foo/test", port1); - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicTest() throws Exception { - AsyncHttpClientConfig.Builder b = new AsyncHttpClientConfig.Builder(); - b.addRequestFilter(new ThrottleRequestFilter(100)); - - AsyncHttpClient c = getAsyncHttpClient(b.build()); - try { - Response response = c.preparePost(getTargetUrl()).execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void loadThrottleTest() throws Exception { - AsyncHttpClientConfig.Builder b = new AsyncHttpClientConfig.Builder(); - b.addRequestFilter(new ThrottleRequestFilter(10)); - - AsyncHttpClient c = getAsyncHttpClient(b.build()); - try { - List> futures = new ArrayList>(); - for (int i = 0; i < 200; i++) { - futures.add(c.preparePost(getTargetUrl()).execute()); - } - - for (Future f : futures) { - Response r = f.get(); - assertNotNull(f.get()); - assertEquals(r.getStatusCode(), 200); - } - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void maxConnectionsText() throws Exception { - AsyncHttpClientConfig.Builder b = new AsyncHttpClientConfig.Builder(); - b.addRequestFilter(new ThrottleRequestFilter(0, 1000)); - AsyncHttpClient c = getAsyncHttpClient(b.build()); - - try { - /* Response response = */c.preparePost(getTargetUrl()).execute().get(); - fail("Should have timed out"); - } catch (IOException ex) { - assertNotNull(ex); - assertEquals(ex.getCause().getClass(), FilterException.class); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicResponseFilterTest() throws Exception { - AsyncHttpClientConfig.Builder b = new AsyncHttpClientConfig.Builder(); - b.addResponseFilter(new ResponseFilter() { - - // @Override - public FilterContext filter(FilterContext ctx) throws FilterException { - return ctx; - } - - }); - AsyncHttpClient c = getAsyncHttpClient(b.build()); - - try { - Response response = c.preparePost(getTargetUrl()).execute().get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - } catch (IOException ex) { - fail("Should have timed out"); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void replayResponseFilterTest() throws Exception { - AsyncHttpClientConfig.Builder b = new AsyncHttpClientConfig.Builder(); - final AtomicBoolean replay = new AtomicBoolean(true); - - b.addResponseFilter(new ResponseFilter() { - - public FilterContext filter(FilterContext ctx) throws FilterException { - - if (replay.getAndSet(false)) { - Request request = new RequestBuilder(ctx.getRequest()).addHeader("X-Replay", "true").build(); - return new FilterContext.FilterContextBuilder().asyncHandler(ctx.getAsyncHandler()).request(request).replayRequest(true).build(); - } - return ctx; - } - - }); - AsyncHttpClient c = getAsyncHttpClient(b.build()); - - try { - Response response = c.preparePost(getTargetUrl()).execute().get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("X-Replay"), "true"); - } catch (IOException ex) { - fail("Should have timed out"); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void replayStatusCodeResponseFilterTest() throws Exception { - AsyncHttpClientConfig.Builder b = new AsyncHttpClientConfig.Builder(); - final AtomicBoolean replay = new AtomicBoolean(true); - - b.addResponseFilter(new ResponseFilter() { - - public FilterContext filter(FilterContext ctx) throws FilterException { - - if (ctx.getResponseStatus() != null && ctx.getResponseStatus().getStatusCode() == 200 && replay.getAndSet(false)) { - Request request = new RequestBuilder(ctx.getRequest()).addHeader("X-Replay", "true").build(); - return new FilterContext.FilterContextBuilder().asyncHandler(ctx.getAsyncHandler()).request(request).replayRequest(true).build(); - } - return ctx; - } - - }); - AsyncHttpClient c = getAsyncHttpClient(b.build()); - - try { - Response response = c.preparePost(getTargetUrl()).execute().get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("X-Replay"), "true"); - } catch (IOException ex) { - fail("Should have timed out"); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void replayHeaderResponseFilterTest() throws Exception { - AsyncHttpClientConfig.Builder b = new AsyncHttpClientConfig.Builder(); - final AtomicBoolean replay = new AtomicBoolean(true); - - b.addResponseFilter(new ResponseFilter() { - - public FilterContext filter(FilterContext ctx) throws FilterException { - - if (ctx.getResponseHeaders() != null && ctx.getResponseHeaders().getHeaders().getFirstValue("Ping").equals("Pong") && replay.getAndSet(false)) { - - Request request = new RequestBuilder(ctx.getRequest()).addHeader("Ping", "Pong").build(); - return new FilterContext.FilterContextBuilder().asyncHandler(ctx.getAsyncHandler()).request(request).replayRequest(true).build(); - } - return ctx; - } - - }); - AsyncHttpClient c = getAsyncHttpClient(b.build()); - - try { - Response response = c.preparePost(getTargetUrl()).addHeader("Ping", "Pong").execute().get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("Ping"), "Pong"); - } catch (IOException ex) { - fail("Should have timed out"); - } finally { - c.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/FluentCaseInsensitiveStringsMapTest.java b/api/src/test/java/org/asynchttpclient/async/FluentCaseInsensitiveStringsMapTest.java deleted file mode 100644 index 51dee68600..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/FluentCaseInsensitiveStringsMapTest.java +++ /dev/null @@ -1,625 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.testng.annotations.Test; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; - -public class FluentCaseInsensitiveStringsMapTest { - - @Test - public void emptyTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - assertTrue(map.keySet().isEmpty()); - } - - @Test - public void normalTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void nameCaseTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("fOO", "bAr"); - map.add("Baz", Arrays.asList("fOo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("fOO", "Baz"))); - - assertEquals(map.getFirstValue("fOO"), "bAr"); - assertEquals(map.getJoinedValue("fOO", ", "), "bAr"); - assertEquals(map.get("fOO"), Arrays.asList("bAr")); - assertEquals(map.getFirstValue("foo"), "bAr"); - assertEquals(map.getJoinedValue("foo", ", "), "bAr"); - assertEquals(map.get("foo"), Arrays.asList("bAr")); - assertEquals(map.getFirstValue("FOO"), "bAr"); - assertEquals(map.getJoinedValue("FOO", ", "), "bAr"); - assertEquals(map.get("FOO"), Arrays.asList("bAr")); - - assertEquals(map.getFirstValue("Baz"), "fOo"); - assertEquals(map.getJoinedValue("Baz", ", "), "fOo, bar"); - assertEquals(map.get("Baz"), Arrays.asList("fOo", "bar")); - assertEquals(map.getFirstValue("baz"), "fOo"); - assertEquals(map.getJoinedValue("baz", ", "), "fOo, bar"); - assertEquals(map.get("baz"), Arrays.asList("fOo", "bar")); - assertEquals(map.getFirstValue("BAZ"), "fOo"); - assertEquals(map.getJoinedValue("BAZ", ", "), "fOo, bar"); - assertEquals(map.get("BAZ"), Arrays.asList("fOo", "bar")); - } - - @Test - public void sameKeyMultipleTimesTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "baz,foo"); - map.add("Foo", Arrays.asList("bar")); - map.add("fOO", "bla", "blubb"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo"))); - - assertEquals(map.getFirstValue("foo"), "baz,foo"); - assertEquals(map.getJoinedValue("foo", ", "), "baz,foo, bar, bla, blubb"); - assertEquals(map.get("foo"), Arrays.asList("baz,foo", "bar", "bla", "blubb")); - assertEquals(map.getFirstValue("Foo"), "baz,foo"); - assertEquals(map.getJoinedValue("Foo", ", "), "baz,foo, bar, bla, blubb"); - assertEquals(map.get("Foo"), Arrays.asList("baz,foo", "bar", "bla", "blubb")); - assertEquals(map.getFirstValue("fOO"), "baz,foo"); - assertEquals(map.getJoinedValue("fOO", ", "), "baz,foo, bar, bla, blubb"); - assertEquals(map.get("fOO"), Arrays.asList("baz,foo", "bar", "bla", "blubb")); - } - - @Test - public void emptyValueTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", ""); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo"))); - assertEquals(map.getFirstValue("foo"), ""); - assertEquals(map.getJoinedValue("foo", ", "), ""); - assertEquals(map.get("foo"), Arrays.asList("")); - } - - @Test - public void nullValueTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", (String) null); - - assertEquals(map.getFirstValue("foo"), ""); - assertEquals(map.getJoinedValue("foo", ", "), ""); - assertEquals(map.get("foo").size(), 1); - } - - @Test - public void mapConstructorTest() { - Map> headerMap = new LinkedHashMap>(); - - headerMap.put("foo", Arrays.asList("baz,foo")); - headerMap.put("baz", Arrays.asList("bar")); - headerMap.put("bar", Arrays.asList("bla", "blubb")); - - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(headerMap); - - headerMap.remove("foo"); - headerMap.remove("bar"); - headerMap.remove("baz"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz", "bar"))); - assertEquals(map.getFirstValue("foo"), "baz,foo"); - assertEquals(map.getJoinedValue("foo", ", "), "baz,foo"); - assertEquals(map.get("foo"), Arrays.asList("baz,foo")); - assertEquals(map.getFirstValue("baz"), "bar"); - assertEquals(map.getJoinedValue("baz", ", "), "bar"); - assertEquals(map.get("baz"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "bla"); - assertEquals(map.getJoinedValue("bar", ", "), "bla, blubb"); - assertEquals(map.get("bar"), Arrays.asList("bla", "blubb")); - } - - @Test - public void mapConstructorNullTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap((Map>) null); - - assertEquals(map.keySet().size(), 0); - } - - @Test - public void copyConstructorTest() { - FluentCaseInsensitiveStringsMap srcHeaders = new FluentCaseInsensitiveStringsMap(); - - srcHeaders.add("foo", "baz,foo"); - srcHeaders.add("baz", Arrays.asList("bar")); - srcHeaders.add("bar", "bla", "blubb"); - - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(srcHeaders); - - srcHeaders.delete("foo"); - srcHeaders.delete("bar"); - srcHeaders.delete("baz"); - assertTrue(srcHeaders.keySet().isEmpty()); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz", "bar"))); - assertEquals(map.getFirstValue("foo"), "baz,foo"); - assertEquals(map.getJoinedValue("foo", ", "), "baz,foo"); - assertEquals(map.get("foo"), Arrays.asList("baz,foo")); - assertEquals(map.getFirstValue("baz"), "bar"); - assertEquals(map.getJoinedValue("baz", ", "), "bar"); - assertEquals(map.get("baz"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "bla"); - assertEquals(map.getJoinedValue("bar", ", "), "bla, blubb"); - assertEquals(map.get("bar"), Arrays.asList("bla", "blubb")); - } - - @Test - public void copyConstructorNullTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap((FluentCaseInsensitiveStringsMap) null); - - assertEquals(map.keySet().size(), 0); - } - - @Test - public void deleteTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.delete("bAz"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertNull(map.getFirstValue("baz")); - assertNull(map.getJoinedValue("baz", ", ")); - assertNull(map.get("baz")); - } - - @Test - public void deleteUndefinedKeyTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.delete("bar"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void deleteNullTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.delete(null); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void deleteAllArrayTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.deleteAll("bAz", "Boo"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertNull(map.getFirstValue("baz")); - assertNull(map.getJoinedValue("baz", ", ")); - assertNull(map.get("baz")); - } - - @Test - public void deleteAllCollectionTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.deleteAll(Arrays.asList("bAz", "fOO")); - - assertEquals(map.keySet(), Collections. emptyList()); - assertNull(map.getFirstValue("foo")); - assertNull(map.getJoinedValue("foo", ", ")); - assertNull(map.get("foo")); - assertNull(map.getFirstValue("baz")); - assertNull(map.getJoinedValue("baz", ", ")); - assertNull(map.get("baz")); - } - - @Test - public void deleteAllNullArrayTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.deleteAll((String[]) null); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void deleteAllNullCollectionTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.deleteAll((Collection) null); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void replaceTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replace("Foo", "blub", "bla"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("Foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "blub"); - assertEquals(map.getJoinedValue("foo", ", "), "blub, bla"); - assertEquals(map.get("foo"), Arrays.asList("blub", "bla")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void replaceUndefinedTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replace("bar", Arrays.asList("blub")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz", "bar"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - assertEquals(map.getFirstValue("bar"), "blub"); - assertEquals(map.getJoinedValue("bar", ", "), "blub"); - assertEquals(map.get("bar"), Arrays.asList("blub")); - } - - @Test - public void replaceNullTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replace(null, Arrays.asList("blub")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void replaceValueWithNullTest() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replace("baZ", (Collection) null); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertNull(map.getFirstValue("baz")); - assertNull(map.getJoinedValue("baz", ", ")); - assertNull(map.get("baz")); - } - - @Test - public void replaceAllMapTest1() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("bar", "foo, bar", "baz"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "bar", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "foo, bar"); - assertEquals(map.getJoinedValue("bar", ", "), "foo, bar, baz"); - assertEquals(map.get("bar"), Arrays.asList("foo, bar", "baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replaceAll(new FluentCaseInsensitiveStringsMap().add("Bar", "baz").add("Boo", "blub", "bla")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "Bar", "baz", "Boo"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "baz"); - assertEquals(map.getJoinedValue("bar", ", "), "baz"); - assertEquals(map.get("bar"), Arrays.asList("baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - assertEquals(map.getFirstValue("Boo"), "blub"); - assertEquals(map.getJoinedValue("Boo", ", "), "blub, bla"); - assertEquals(map.get("Boo"), Arrays.asList("blub", "bla")); - } - - @Test - public void replaceAllTest2() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("bar", "foo, bar", "baz"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "bar", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "foo, bar"); - assertEquals(map.getJoinedValue("bar", ", "), "foo, bar, baz"); - assertEquals(map.get("bar"), Arrays.asList("foo, bar", "baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - LinkedHashMap> newValues = new LinkedHashMap>(); - - newValues.put("Bar", Arrays.asList("baz")); - newValues.put("Foo", null); - map.replaceAll(newValues); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("Bar", "baz"))); - assertNull(map.getFirstValue("foo")); - assertNull(map.getJoinedValue("foo", ", ")); - assertNull(map.get("foo")); - assertEquals(map.getFirstValue("bar"), "baz"); - assertEquals(map.getJoinedValue("bar", ", "), "baz"); - assertEquals(map.get("bar"), Arrays.asList("baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void replaceAllNullTest1() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("bar", "foo, bar", "baz"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "bar", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "foo, bar"); - assertEquals(map.getJoinedValue("bar", ", "), "foo, bar, baz"); - assertEquals(map.get("bar"), Arrays.asList("foo, bar", "baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replaceAll((FluentCaseInsensitiveStringsMap) null); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "bar", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "foo, bar"); - assertEquals(map.getJoinedValue("bar", ", "), "foo, bar, baz"); - assertEquals(map.get("bar"), Arrays.asList("foo, bar", "baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void replaceAllNullTest2() { - FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); - - map.add("foo", "bar"); - map.add("bar", "foo, bar", "baz"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "bar", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "foo, bar"); - assertEquals(map.getJoinedValue("bar", ", "), "foo, bar, baz"); - assertEquals(map.get("bar"), Arrays.asList("foo, bar", "baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replaceAll((Map>) null); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "bar", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "foo, bar"); - assertEquals(map.getJoinedValue("bar", ", "), "foo, bar, baz"); - assertEquals(map.get("bar"), Arrays.asList("foo, bar", "baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/FluentStringsMapTest.java b/api/src/test/java/org/asynchttpclient/async/FluentStringsMapTest.java deleted file mode 100644 index e9568773c6..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/FluentStringsMapTest.java +++ /dev/null @@ -1,755 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.FluentStringsMap; -import org.testng.annotations.Test; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; - -public class FluentStringsMapTest { - - @Test - public void emptyTest() { - FluentStringsMap map = new FluentStringsMap(); - - assertTrue(map.keySet().isEmpty()); - } - - @Test - public void normalTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("fOO", "bAr"); - map.add("Baz", Arrays.asList("fOo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("fOO", "Baz"))); - - assertEquals(map.getFirstValue("fOO"), "bAr"); - assertEquals(map.getJoinedValue("fOO", ", "), "bAr"); - assertEquals(map.get("fOO"), Arrays.asList("bAr")); - assertNull(map.getFirstValue("foo")); - assertNull(map.getJoinedValue("foo", ", ")); - assertNull(map.get("foo")); - - assertEquals(map.getFirstValue("Baz"), "fOo"); - assertEquals(map.getJoinedValue("Baz", ", "), "fOo, bar"); - assertEquals(map.get("Baz"), Arrays.asList("fOo", "bar")); - assertNull(map.getFirstValue("baz")); - assertNull(map.getJoinedValue("baz", ", ")); - assertNull(map.get("baz")); - } - - @Test - public void addNullTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("fOO", "bAr"); - map.add(null, Arrays.asList("fOo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("fOO"))); - - assertEquals(map.getFirstValue("fOO"), "bAr"); - assertEquals(map.getJoinedValue("fOO", ", "), "bAr"); - assertEquals(map.get("fOO"), Arrays.asList("bAr")); - assertNull(map.getFirstValue("foo")); - assertNull(map.getJoinedValue("foo", ", ")); - assertNull(map.get("foo")); - - assertNull(map.getFirstValue(null)); - assertNull(map.getJoinedValue("Baz", ", ")); - assertNull(map.get(null)); - } - - @Test - public void sameKeyMultipleTimesTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "baz,foo"); - map.add("foo", Arrays.asList("bar")); - map.add("foo", "bla", "blubb"); - map.add("fOO", "duh"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "fOO"))); - - assertEquals(map.getFirstValue("foo"), "baz,foo"); - assertEquals(map.getJoinedValue("foo", ", "), "baz,foo, bar, bla, blubb"); - assertEquals(map.get("foo"), Arrays.asList("baz,foo", "bar", "bla", "blubb")); - assertEquals(map.getFirstValue("fOO"), "duh"); - assertEquals(map.getJoinedValue("fOO", ", "), "duh"); - assertEquals(map.get("fOO"), Arrays.asList("duh")); - } - - @Test - public void emptyValueTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", ""); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo"))); - assertEquals(map.getFirstValue("foo"), ""); - assertEquals(map.getJoinedValue("foo", ", "), ""); - assertEquals(map.get("foo"), Arrays.asList("")); - } - - @Test - public void nullValueTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", (String) null); - - assertEquals(map.getFirstValue("foo"), null); - assertEquals(map.getJoinedValue("foo", ", "), null); - assertEquals(map.get("foo").size(), 1); - } - - @Test - public void mapConstructorTest() { - Map> headerMap = new LinkedHashMap>(); - - headerMap.put("foo", Arrays.asList("baz,foo")); - headerMap.put("baz", Arrays.asList("bar")); - headerMap.put("bar", Arrays.asList("bla", "blubb")); - - FluentStringsMap map = new FluentStringsMap(headerMap); - - headerMap.remove("foo"); - headerMap.remove("bar"); - headerMap.remove("baz"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz", "bar"))); - assertEquals(map.getFirstValue("foo"), "baz,foo"); - assertEquals(map.getJoinedValue("foo", ", "), "baz,foo"); - assertEquals(map.get("foo"), Arrays.asList("baz,foo")); - assertEquals(map.getFirstValue("baz"), "bar"); - assertEquals(map.getJoinedValue("baz", ", "), "bar"); - assertEquals(map.get("baz"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "bla"); - assertEquals(map.getJoinedValue("bar", ", "), "bla, blubb"); - assertEquals(map.get("bar"), Arrays.asList("bla", "blubb")); - } - - @Test - public void mapConstructorNullTest() { - FluentStringsMap map = new FluentStringsMap((Map>) null); - - assertEquals(map.keySet().size(), 0); - } - - @Test - public void copyConstructorTest() { - FluentStringsMap srcHeaders = new FluentStringsMap(); - - srcHeaders.add("foo", "baz,foo"); - srcHeaders.add("baz", Arrays.asList("bar")); - srcHeaders.add("bar", "bla", "blubb"); - - FluentStringsMap map = new FluentStringsMap(srcHeaders); - - srcHeaders.delete("foo"); - srcHeaders.delete("bar"); - srcHeaders.delete("baz"); - assertTrue(srcHeaders.keySet().isEmpty()); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz", "bar"))); - assertEquals(map.getFirstValue("foo"), "baz,foo"); - assertEquals(map.getJoinedValue("foo", ", "), "baz,foo"); - assertEquals(map.get("foo"), Arrays.asList("baz,foo")); - assertEquals(map.getFirstValue("baz"), "bar"); - assertEquals(map.getJoinedValue("baz", ", "), "bar"); - assertEquals(map.get("baz"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "bla"); - assertEquals(map.getJoinedValue("bar", ", "), "bla, blubb"); - assertEquals(map.get("bar"), Arrays.asList("bla", "blubb")); - } - - @Test - public void copyConstructorNullTest() { - FluentStringsMap map = new FluentStringsMap((FluentStringsMap) null); - - assertEquals(map.keySet().size(), 0); - } - - @Test - public void deleteTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.delete("baz"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertNull(map.getFirstValue("baz")); - assertNull(map.getJoinedValue("baz", ", ")); - assertNull(map.get("baz")); - } - - @Test - public void deleteTestDifferentCase() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.delete("bAz"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void deleteUndefinedKeyTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.delete("bar"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void deleteNullTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.delete(null); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void deleteAllArrayTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.deleteAll("baz", "Boo"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertNull(map.getFirstValue("baz")); - assertNull(map.getJoinedValue("baz", ", ")); - assertNull(map.get("baz")); - } - - @Test - public void deleteAllArrayDifferentCaseTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.deleteAll("Foo", "baz"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertNull(map.getFirstValue("baz")); - assertNull(map.getJoinedValue("baz", ", ")); - assertNull(map.get("baz")); - } - - @Test - public void deleteAllCollectionTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.deleteAll(Arrays.asList("baz", "foo")); - - assertEquals(map.keySet(), Collections. emptyList()); - assertNull(map.getFirstValue("foo")); - assertNull(map.getJoinedValue("foo", ", ")); - assertNull(map.get("foo")); - assertNull(map.getFirstValue("baz")); - assertNull(map.getJoinedValue("baz", ", ")); - assertNull(map.get("baz")); - } - - @Test - public void deleteAllCollectionDifferentCaseTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.deleteAll(Arrays.asList("bAz", "fOO")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void deleteAllNullArrayTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.deleteAll((String[]) null); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void deleteAllNullCollectionTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.deleteAll((Collection) null); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void replaceArrayTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replace("foo", "blub", "bla"); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "blub"); - assertEquals(map.getJoinedValue("foo", ", "), "blub, bla"); - assertEquals(map.get("foo"), Arrays.asList("blub", "bla")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void replaceCollectionTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replace("foo", Arrays.asList("blub", "bla")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "blub"); - assertEquals(map.getJoinedValue("foo", ", "), "blub, bla"); - assertEquals(map.get("foo"), Arrays.asList("blub", "bla")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void replaceDifferentCaseTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replace("Foo", Arrays.asList("blub", "bla")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz", "Foo"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - assertEquals(map.getFirstValue("Foo"), "blub"); - assertEquals(map.getJoinedValue("Foo", ", "), "blub, bla"); - assertEquals(map.get("Foo"), Arrays.asList("blub", "bla")); - } - - @Test - public void replaceUndefinedTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replace("bar", Arrays.asList("blub")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz", "bar"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - assertEquals(map.getFirstValue("bar"), "blub"); - assertEquals(map.getJoinedValue("bar", ", "), "blub"); - assertEquals(map.get("bar"), Arrays.asList("blub")); - } - - @Test - public void replaceNullTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replace(null, Arrays.asList("blub")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void replaceValueWithNullTest() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replace("baz", (Collection) null); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertNull(map.getFirstValue("baz")); - assertNull(map.getJoinedValue("baz", ", ")); - assertNull(map.get("baz")); - } - - @Test - public void replaceAllMapTest1() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("bar", "foo, bar", "baz"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "bar", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "foo, bar"); - assertEquals(map.getJoinedValue("bar", ", "), "foo, bar, baz"); - assertEquals(map.get("bar"), Arrays.asList("foo, bar", "baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replaceAll(new FluentStringsMap().add("bar", "baz").add("Foo", "blub", "bla")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "bar", "baz", "Foo"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "baz"); - assertEquals(map.getJoinedValue("bar", ", "), "baz"); - assertEquals(map.get("bar"), Arrays.asList("baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - assertEquals(map.getFirstValue("Foo"), "blub"); - assertEquals(map.getJoinedValue("Foo", ", "), "blub, bla"); - assertEquals(map.get("Foo"), Arrays.asList("blub", "bla")); - } - - @Test - public void replaceAllTest2() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("bar", "foo, bar", "baz"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "bar", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "foo, bar"); - assertEquals(map.getJoinedValue("bar", ", "), "foo, bar, baz"); - assertEquals(map.get("bar"), Arrays.asList("foo, bar", "baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - LinkedHashMap> newValues = new LinkedHashMap>(); - - newValues.put("bar", Arrays.asList("baz")); - newValues.put("foo", null); - map.replaceAll(newValues); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("bar", "baz"))); - assertNull(map.getFirstValue("foo")); - assertNull(map.getJoinedValue("foo", ", ")); - assertNull(map.get("foo")); - assertEquals(map.getFirstValue("bar"), "baz"); - assertEquals(map.getJoinedValue("bar", ", "), "baz"); - assertEquals(map.get("bar"), Arrays.asList("baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void replaceAllNullTest1() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("bar", "foo, bar", "baz"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "bar", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "foo, bar"); - assertEquals(map.getJoinedValue("bar", ", "), "foo, bar, baz"); - assertEquals(map.get("bar"), Arrays.asList("foo, bar", "baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replaceAll((FluentStringsMap) null); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "bar", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "foo, bar"); - assertEquals(map.getJoinedValue("bar", ", "), "foo, bar, baz"); - assertEquals(map.get("bar"), Arrays.asList("foo, bar", "baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } - - @Test - public void replaceAllNullTest2() { - FluentStringsMap map = new FluentStringsMap(); - - map.add("foo", "bar"); - map.add("bar", "foo, bar", "baz"); - map.add("baz", Arrays.asList("foo", "bar")); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "bar", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "foo, bar"); - assertEquals(map.getJoinedValue("bar", ", "), "foo, bar, baz"); - assertEquals(map.get("bar"), Arrays.asList("foo, bar", "baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - - map.replaceAll((Map>) null); - - assertEquals(map.keySet(), new LinkedHashSet(Arrays.asList("foo", "bar", "baz"))); - assertEquals(map.getFirstValue("foo"), "bar"); - assertEquals(map.getJoinedValue("foo", ", "), "bar"); - assertEquals(map.get("foo"), Arrays.asList("bar")); - assertEquals(map.getFirstValue("bar"), "foo, bar"); - assertEquals(map.getJoinedValue("bar", ", "), "foo, bar, baz"); - assertEquals(map.get("bar"), Arrays.asList("foo, bar", "baz")); - assertEquals(map.getFirstValue("baz"), "foo"); - assertEquals(map.getJoinedValue("baz", ", "), "foo, bar"); - assertEquals(map.get("baz"), Arrays.asList("foo", "bar")); - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/FollowingThreadTest.java b/api/src/test/java/org/asynchttpclient/async/FollowingThreadTest.java deleted file mode 100644 index 2897d772e3..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/FollowingThreadTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeoutException; - -/** - * Simple stress test for exercising the follow redirect. - */ -public abstract class FollowingThreadTest extends AbstractBasicTest { - - private static final int COUNT = 10; - - @Test(timeOut = 30 * 1000, groups = { "online", "default_provider", "scalability" }) - public void testFollowRedirect() throws IOException, ExecutionException, TimeoutException, InterruptedException { - - final CountDownLatch countDown = new CountDownLatch(COUNT); - ExecutorService pool = Executors.newCachedThreadPool(); - try { - for (int i = 0; i < COUNT; i++) { - pool.submit(new Runnable() { - - private int status; - - public void run() { - final CountDownLatch l = new CountDownLatch(1); - final AsyncHttpClient ahc = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build()); - try { - ahc.prepareGet("http://www.google.com/").execute(new AsyncHandler() { - - public void onThrowable(Throwable t) { - t.printStackTrace(); - } - - public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - System.out.println(new String(bodyPart.getBodyPartBytes())); - return STATE.CONTINUE; - } - - public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { - status = responseStatus.getStatusCode(); - System.out.println(responseStatus.getStatusText()); - return STATE.CONTINUE; - } - - public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { - return STATE.CONTINUE; - } - - public Integer onCompleted() throws Exception { - l.countDown(); - return status; - } - }); - - l.await(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - ahc.close(); - countDown.countDown(); - } - } - }); - } - countDown.await(); - } finally { - pool.shutdown(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/Head302Test.java b/api/src/test/java/org/asynchttpclient/async/Head302Test.java deleted file mode 100644 index 4ef5b2e3ec..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/Head302Test.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncCompletionHandlerBase; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.concurrent.BrokenBarrierException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * Tests HEAD request that gets 302 response. - * - * @author Hubert Iwaniuk - */ -public abstract class Head302Test extends AbstractBasicTest { - - /** - * Handler that does Found (302) in response to HEAD method. - */ - private static class Head302handler extends AbstractHandler { - public void handle(String s, org.eclipse.jetty.server.Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - if ("HEAD".equalsIgnoreCase(request.getMethod())) { - if (request.getPathInfo().endsWith("_moved")) { - response.setStatus(HttpServletResponse.SC_OK); - } else { - response.setStatus(HttpServletResponse.SC_FOUND); // 302 - response.setHeader("Location", request.getPathInfo() + "_moved"); - } - } else { // this handler is to handle HEAD request - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - } - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new Head302handler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void testHEAD302() throws IOException, BrokenBarrierException, InterruptedException, ExecutionException, TimeoutException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final CountDownLatch l = new CountDownLatch(1); - Request request = new RequestBuilder("HEAD").setUrl("http://127.0.0.1/" + port1 + "/Test").build(); - - client.executeRequest(request, new AsyncCompletionHandlerBase() { - @Override - public Response onCompleted(Response response) throws Exception { - l.countDown(); - return super.onCompleted(response); - } - }).get(3, TimeUnit.SECONDS); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/HostnameVerifierTest.java b/api/src/test/java/org/asynchttpclient/async/HostnameVerifierTest.java deleted file mode 100644 index 083b2da0f6..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/HostnameVerifierTest.java +++ /dev/null @@ -1,231 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.SIMPLE_TEXT_FILE; -import static org.asynchttpclient.async.util.TestUtils.SIMPLE_TEXT_FILE_STRING; -import static org.asynchttpclient.async.util.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; -import static org.asynchttpclient.async.util.TestUtils.createSSLContext; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig.Builder; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testng.annotations.Test; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.net.ConnectException; -import java.util.Enumeration; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; - -public abstract class HostnameVerifierTest extends AbstractBasicHttpsTest { - - protected final Logger log = LoggerFactory.getLogger(HostnameVerifierTest.class); - - public static class EchoHandler extends AbstractHandler { - - @Override - public void handle(String pathInContext, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException, IOException { - - httpResponse.setContentType(TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); - Enumeration e = httpRequest.getHeaderNames(); - String param; - while (e.hasMoreElements()) { - param = e.nextElement().toString(); - - if (param.startsWith("LockThread")) { - try { - Thread.sleep(40 * 1000); - } catch (InterruptedException ex) { // nothing to do here - } - } - - httpResponse.addHeader("X-" + param, httpRequest.getHeader(param)); - } - - Enumeration i = httpRequest.getParameterNames(); - - StringBuilder requestBody = new StringBuilder(); - while (i.hasMoreElements()) { - param = i.nextElement().toString(); - httpResponse.addHeader("X-" + param, httpRequest.getParameter(param)); - requestBody.append(param); - requestBody.append("_"); - } - - String pathInfo = httpRequest.getPathInfo(); - if (pathInfo != null) - httpResponse.addHeader("X-pathInfo", pathInfo); - - String queryString = httpRequest.getQueryString(); - if (queryString != null) - httpResponse.addHeader("X-queryString", queryString); - - httpResponse.addHeader("X-KEEP-ALIVE", httpRequest.getRemoteAddr() + ":" + httpRequest.getRemotePort()); - - javax.servlet.http.Cookie[] cs = httpRequest.getCookies(); - if (cs != null) { - for (javax.servlet.http.Cookie c : cs) { - httpResponse.addCookie(c); - } - } - - if (requestBody.length() > 0) { - httpResponse.getOutputStream().write(requestBody.toString().getBytes()); - } - - int size = 10 * 1024; - if (httpRequest.getContentLength() > 0) { - size = httpRequest.getContentLength(); - } - byte[] bytes = new byte[size]; - int pos = 0; - if (bytes.length > 0) { - int read = 0; - while (read != -1) { - read = httpRequest.getInputStream().read(bytes, pos, bytes.length - pos); - pos += read; - } - - httpResponse.getOutputStream().write(bytes); - } - - httpResponse.setStatus(200); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - - } - } - - protected String getTargetUrl() { - return String.format("https://127.0.0.1:%d/foo/test", port1); - } - - public AbstractHandler configureHandler() throws Exception { - return new EchoHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void positiveHostnameVerifierTest() throws Exception { - - final AsyncHttpClient client = getAsyncHttpClient(new Builder().setHostnameVerifier(new PositiveHostVerifier()).setSSLContext(createSSLContext(new AtomicBoolean(true))).build()); - try { - Future f = client.preparePost(getTargetUrl()).setBody(SIMPLE_TEXT_FILE).setHeader("Content-Type", "text/html").execute(); - Response resp = f.get(); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void negativeHostnameVerifierTest() throws Exception { - - final AsyncHttpClient client = getAsyncHttpClient(new Builder().setHostnameVerifier(new NegativeHostVerifier()).setSSLContext(createSSLContext(new AtomicBoolean(true))).build()); - try { - try { - client.preparePost(getTargetUrl()).setBody(SIMPLE_TEXT_FILE).setHeader("Content-Type", "text/html").execute().get(); - fail("ConnectException expected"); - } catch (ExecutionException ex) { - assertEquals(ex.getCause().getClass(), ConnectException.class); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void remoteIDHostnameVerifierTest() throws Exception { - - final AsyncHttpClient client = getAsyncHttpClient(new Builder().setHostnameVerifier(new CheckHost("bouette")).setSSLContext(createSSLContext(new AtomicBoolean(true))).build()); - try { - client.preparePost(getTargetUrl()).setBody(SIMPLE_TEXT_FILE).setHeader("Content-Type", "text/html").execute().get(); - fail("ConnectException expected"); - } catch (ExecutionException ex) { - assertEquals(ex.getCause().getClass(), ConnectException.class); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void remoteNegHostnameVerifierTest() throws Exception { - // request is made to 127.0.0.1, but cert presented for localhost - this should fail - final AsyncHttpClient client = getAsyncHttpClient(new Builder().setHostnameVerifier(new CheckHost("localhost")).setSSLContext(createSSLContext(new AtomicBoolean(true))).build()); - try { - client.preparePost(getTargetUrl()).setBody(SIMPLE_TEXT_FILE).setHeader("Content-Type", "text/html").execute().get(); - fail("ConnectException expected"); - } catch (ExecutionException ex) { - assertEquals(ex.getCause().getClass(), ConnectException.class); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void remotePosHostnameVerifierTest() throws Exception { - - final AsyncHttpClient client = getAsyncHttpClient(new Builder().setHostnameVerifier(new CheckHost("127.0.0.1")).setSSLContext(createSSLContext(new AtomicBoolean(true))).build()); - try { - Response resp = client.preparePost(getTargetUrl()).setBody(SIMPLE_TEXT_FILE).setHeader("Content-Type", "text/html").execute().get(); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); - } finally { - client.close(); - } - } - - public static class PositiveHostVerifier implements HostnameVerifier { - - public boolean verify(String s, SSLSession sslSession) { - return true; - } - } - - public static class NegativeHostVerifier implements HostnameVerifier { - - public boolean verify(String s, SSLSession sslSession) { - return false; - } - } - - public static class CheckHost implements HostnameVerifier { - - private final String hostName; - - public CheckHost(String hostName) { - this.hostName = hostName; - } - - public boolean verify(String s, SSLSession sslSession) { - return s != null && s.equalsIgnoreCase(hostName); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java b/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java deleted file mode 100644 index f186cbb3dd..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; -import static org.asynchttpclient.async.util.TestUtils.addHttpsConnector; -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.Enumeration; -import java.util.concurrent.atomic.AtomicBoolean; - -public abstract class HttpToHttpsRedirectTest extends AbstractBasicTest { - - private final AtomicBoolean redirectDone = new AtomicBoolean(false); - - private class Relative302Handler extends AbstractHandler { - - public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { - - String param; - httpResponse.setContentType(TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); - Enumeration e = httpRequest.getHeaderNames(); - while (e.hasMoreElements()) { - param = e.nextElement().toString(); - - if (param.startsWith("X-redirect") && !redirectDone.getAndSet(true)) { - httpResponse.addHeader("Location", httpRequest.getHeader(param)); - httpResponse.setStatus(302); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - return; - } - } - - if (r.getScheme().equalsIgnoreCase("https")) { - httpResponse.addHeader("X-httpToHttps", "PASS"); - redirectDone.getAndSet(false); - } - - httpResponse.setStatus(200); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - } - } - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - port2 = findFreePort(); - - server = newJettyHttpServer(port1); - addHttpsConnector(server, port2); - server.setHandler(new Relative302Handler()); - server.start(); - logger.info("Local HTTP server started successfully"); - } - - @Test(groups = { "standalone", "default_provider" }) - // FIXME find a way to make this threadsafe, other, set @Test(singleThreaded = true) - public void runAllSequentiallyBecauseNotThreadSafe() throws Exception { - httpToHttpsRedirect(); - httpToHttpsProperConfig(); - relativeLocationUrl(); - } - - // @Test(groups = { "standalone", "default_provider" }) - public void httpToHttpsRedirect() throws Exception { - redirectDone.getAndSet(false); - - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder()// - .setMaxRedirects(5)// - .setFollowRedirect(true)// - .setAcceptAnyCertificate(true)// - .build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - try { - Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", getTargetUrl2()).execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("X-httpToHttps"), "PASS"); - } finally { - c.close(); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void httpToHttpsProperConfig() throws Exception { - redirectDone.getAndSet(false); - - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder()// - .setMaxRedirects(5)// - .setFollowRedirect(true)// - .setAcceptAnyCertificate(true)// - .build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - try { - Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", getTargetUrl2() + "/test2").execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("X-httpToHttps"), "PASS"); - - // Test if the internal channel is downgraded to clean http. - response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", getTargetUrl2() + "/foo2").execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("X-httpToHttps"), "PASS"); - } finally { - c.close(); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void relativeLocationUrl() throws Exception { - redirectDone.getAndSet(false); - - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder()// - .setMaxRedirects(5)// - .setFollowRedirect(true)// - .setAcceptAnyCertificate(true)// - .build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - try { - Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", "/foo/test").execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 302); - assertEquals(response.getUri().toString(), getTargetUrl()); - } finally { - c.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/IdleStateHandlerTest.java b/api/src/test/java/org/asynchttpclient/async/IdleStateHandlerTest.java deleted file mode 100644 index cefa7083aa..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/IdleStateHandlerTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -public abstract class IdleStateHandlerTest extends AbstractBasicTest { - - private class IdleStateHandler extends AbstractHandler { - - public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { - - try { - Thread.sleep(20 * 1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - httpResponse.setStatus(200); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - } - } - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - server = newJettyHttpServer(port1); - server.setHandler(new IdleStateHandler()); - server.start(); - logger.info("Local HTTP server started successfully"); - } - - @Test(groups = { "online", "default_provider" }) - public void idleStateTest() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setPooledConnectionIdleTimeout(10 * 1000).build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - - try { - c.prepareGet(getTargetUrl()).execute().get(); - } catch (ExecutionException e) { - fail("Should allow to finish processing request.", e); - } finally { - c.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/InputStreamTest.java b/api/src/test/java/org/asynchttpclient/async/InputStreamTest.java deleted file mode 100644 index bd5b47284f..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/InputStreamTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; - -public abstract class InputStreamTest extends AbstractBasicTest { - - private static class InputStreamHandler extends AbstractHandler { - public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - if ("POST".equalsIgnoreCase(request.getMethod())) { - byte[] bytes = new byte[3]; - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - int read = 0; - while (read > -1) { - read = request.getInputStream().read(bytes); - if (read > 0) { - bos.write(bytes, 0, read); - } - } - - response.setStatus(HttpServletResponse.SC_OK); - response.addHeader("X-Param", new String(bos.toByteArray())); - } else { // this handler is to handle POST request - response.sendError(HttpServletResponse.SC_FORBIDDEN); - } - response.getOutputStream().flush(); - response.getOutputStream().close(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new InputStreamHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void testInvalidInputStream() throws IOException, ExecutionException, TimeoutException, InterruptedException { - - AsyncHttpClient c = getAsyncHttpClient(null); - try { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - - InputStream is = new InputStream() { - - public int readAllowed; - - @Override - public int available() { - return 1; // Fake - } - - @Override - public int read() throws IOException { - int fakeCount = readAllowed++; - if (fakeCount == 0) { - return (int) 'a'; - } else if (fakeCount == 1) { - return (int) 'b'; - } else if (fakeCount == 2) { - return (int) 'c'; - } else { - return -1; - } - } - }; - - Response resp = c.preparePost(getTargetUrl()).setHeaders(h).setBody(is).execute().get(); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("X-Param"), "abc"); - } finally { - c.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/ListenableFutureTest.java b/api/src/test/java/org/asynchttpclient/async/ListenableFutureTest.java deleted file mode 100644 index 3109ebf96f..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/ListenableFutureTest.java +++ /dev/null @@ -1,57 +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.async; - -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.Response; -import org.testng.annotations.Test; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -public abstract class ListenableFutureTest extends AbstractBasicTest { - - @Test(groups = { "standalone", "default_provider" }) - public void testListenableFuture() throws Exception { - final AtomicInteger statusCode = new AtomicInteger(500); - AsyncHttpClient ahc = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(1); - final ListenableFuture future = ahc.prepareGet(getTargetUrl()).execute(); - future.addListener(new Runnable() { - - public void run() { - try { - statusCode.set(future.get().getStatusCode()); - latch.countDown(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - } - }, Executors.newFixedThreadPool(1)); - - latch.await(10, TimeUnit.SECONDS); - assertEquals(statusCode.get(), 200); - } finally { - ahc.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/MaxConnectionsInThreads.java b/api/src/test/java/org/asynchttpclient/async/MaxConnectionsInThreads.java deleted file mode 100644 index 4f626fd3f9..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/MaxConnectionsInThreads.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.io.OutputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; - -abstract public class MaxConnectionsInThreads extends AbstractBasicTest { - - // FIXME weird - private static URI servletEndpointUri; - - @Test(groups = { "online", "default_provider" }) - public void testMaxConnectionsWithinThreads() { - - String[] urls = new String[] { servletEndpointUri.toString(), servletEndpointUri.toString() }; - - final AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeout(1000).setRequestTimeout(5000) - .setAllowPoolingConnections(true).setMaxConnections(1).setMaxConnectionsPerHost(1).build()); - - try { - final Boolean[] caughtError = new Boolean[] { Boolean.FALSE }; - List ts = new ArrayList(); - for (int i = 0; i < urls.length; i++) { - final String url = urls[i]; - Thread t = new Thread() { - public void run() { - try { - client.prepareGet(url).execute(); - } catch (IOException e) { - // assert that 2nd request fails, because maxTotalConnections=1 - // logger.debug(i); - caughtError[0] = true; - logger.error("Exception ", e); - } - } - }; - t.start(); - ts.add(t); - } - - for (Thread t : ts) { - try { - t.join(); - } catch (InterruptedException e) { - logger.error("Exception ", e); - } - } - - // Let the threads finish - try { - Thread.sleep(4500); - } catch (InterruptedException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - - assertTrue(caughtError[0], "Max Connections should have been reached"); - - boolean errorInNotThread = false; - for (int i = 0; i < urls.length; i++) { - final String url = urls[i]; - try { - client.prepareGet(url).execute(); - // client.prepareGet(url).execute(); - } catch (IOException e) { - // assert that 2nd request fails, because maxTotalConnections=1 - // logger.debug(i); - errorInNotThread = true; - System.err.println("============"); - e.printStackTrace(); - System.err.println("============"); - } - } - // Let the request finish - try { - Thread.sleep(2500); - } catch (InterruptedException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - assertTrue(errorInNotThread, "Max Connections should have been reached"); - } finally { - client.close(); - } - } - - @Override - @BeforeClass - public void setUpGlobal() throws Exception { - - port1 = findFreePort(); - server = newJettyHttpServer(port1); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/"); - server.setHandler(context); - context.addServlet(new ServletHolder(new MockTimeoutHttpServlet()), "/timeout/*"); - - server.start(); - - String endpoint = "http://127.0.0.1/" + port1 + "/timeout/"; - servletEndpointUri = new URI(endpoint); - } - - public String getTargetUrl() { - String s = "http://127.0.0.1/" + port1 + "/timeout/"; - try { - servletEndpointUri = new URI(s); - } catch (URISyntaxException e) { - e.printStackTrace(); - } - return s; - } - - @SuppressWarnings("serial") - public static class MockTimeoutHttpServlet extends HttpServlet { - private static final Logger LOGGER = LoggerFactory.getLogger(MockTimeoutHttpServlet.class); - private static final String contentType = "text/plain"; - public static long DEFAULT_TIMEOUT = 2000; - - public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { - res.setStatus(200); - res.addHeader("Content-Type", contentType); - long sleepTime = DEFAULT_TIMEOUT; - try { - sleepTime = Integer.parseInt(req.getParameter("timeout")); - - } catch (NumberFormatException e) { - sleepTime = DEFAULT_TIMEOUT; - } - - try { - LOGGER.debug("======================================="); - LOGGER.debug("Servlet is sleeping for: " + sleepTime); - LOGGER.debug("======================================="); - Thread.sleep(sleepTime); - LOGGER.debug("======================================="); - LOGGER.debug("Servlet is awake for"); - LOGGER.debug("======================================="); - } catch (Exception e) { - - } - - res.setHeader("XXX", "TripleX"); - - byte[] retVal = "1".getBytes(); - OutputStream os = res.getOutputStream(); - - res.setContentLength(retVal.length); - os.write(retVal); - os.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/MaxTotalConnectionTest.java b/api/src/test/java/org/asynchttpclient/async/MaxTotalConnectionTest.java deleted file mode 100644 index d12b98c613..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/MaxTotalConnectionTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -public abstract class MaxTotalConnectionTest extends AbstractBasicTest { - protected final Logger log = LoggerFactory.getLogger(AbstractBasicTest.class); - - @Test(groups = { "standalone", "default_provider" }) - public void testMaxTotalConnectionsExceedingException() { - String[] urls = new String[] { "http://google.com/", "http://github.com/" }; - - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeout(1000).setRequestTimeout(5000).setAllowPoolingConnections(false).setMaxConnections(1).setMaxConnectionsPerHost(1).build()); - try { - boolean caughtError = false; - for (int i = 0; i < urls.length; i++) { - try { - client.prepareGet(urls[i]).execute(); - } catch (IOException e) { - // assert that 2nd request fails, because maxTotalConnections=1 - assertEquals(i, 1); - caughtError = true; - } - } - assertTrue(caughtError); - } finally { - client.close(); - } - } - - @Test - public void testMaxTotalConnections() { - String[] urls = new String[] { "http://google.com/", "http://lenta.ru/" }; - - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeout(1000).setRequestTimeout(5000).setAllowPoolingConnections(false).setMaxConnections(2).setMaxConnectionsPerHost(1).build()); - try { - for (String url : urls) { - try { - client.prepareGet(url).execute(); - } catch (IOException e) { - fail("Smth wrong with connections handling!"); - } - } - } finally { - client.close(); - } - } - - /** - * JFA: Disable this test for 1.2.0 release as it can easily fail because a request may complete before the second one is made, hence failing. The issue occurs frequently on Linux. - */ - @Test(enabled = false) - public void testMaxTotalConnectionsCorrectExceptionHandling() { - String[] urls = new String[] { "http://google.com/", "http://github.com/" }; - - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeout(1000).setRequestTimeout(5000).setAllowPoolingConnections(false).setMaxConnections(1).setMaxConnectionsPerHost(1).build()); - try { - List> futures = new ArrayList>(); - boolean caughtError = false; - for (int i = 0; i < urls.length; i++) { - try { - Future future = client.prepareGet(urls[i]).execute(); - if (future != null) { - futures.add(future); - } - } catch (IOException e) { - // assert that 2nd request fails, because maxTotalConnections=1 - assertEquals(i, 1); - caughtError = true; - } - } - assertTrue(caughtError); - - // get results of executed requests - for (Future future : futures) { - try { - /* Response res = */future.get(); - } catch (InterruptedException e) { - log.error("Error!", e); - } catch (ExecutionException e) { - log.error("Error!", e); - } - } - - // try to execute once again, expecting that 1 connection is released - caughtError = false; - for (int i = 0; i < urls.length; i++) { - try { - client.prepareGet(urls[i]).execute(); - } catch (IOException e) { - // assert that 2nd request fails, because maxTotalConnections=1 - assertEquals(i, 1); - caughtError = true; - } - } - assertTrue(caughtError); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/MultipartUploadTest.java b/api/src/test/java/org/asynchttpclient/async/MultipartUploadTest.java deleted file mode 100644 index ad063a9782..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/MultipartUploadTest.java +++ /dev/null @@ -1,433 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.getClasspathFile; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.apache.commons.fileupload.FileItemIterator; -import org.apache.commons.fileupload.FileItemStream; -import org.apache.commons.fileupload.FileUploadException; -import org.apache.commons.fileupload.servlet.ServletFileUpload; -import org.apache.commons.fileupload.util.Streams; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.multipart.ByteArrayPart; -import org.asynchttpclient.multipart.FilePart; -import org.asynchttpclient.multipart.StringPart; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.util.StandardCharsets; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; -import java.util.zip.GZIPInputStream; - -/** - * @author dominict - */ -public abstract class MultipartUploadTest extends AbstractBasicTest { - public static byte GZIPTEXT[] = new byte[] { 31, -117, 8, 8, 11, 43, 79, 75, 0, 3, 104, 101, 108, 108, 111, 46, 116, 120, 116, 0, -53, 72, -51, -55, -55, -25, 2, 0, 32, 48, - 58, 54, 6, 0, 0, 0 }; - - @BeforeClass - public void setUp() throws Exception { - port1 = findFreePort(); - - server = newJettyHttpServer(port1); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.addServlet(new ServletHolder(new MockMultipartUploadServlet()), "/upload/*"); - - server.setHandler(context); - server.start(); - } - - /** - * Tests that the streaming of a file works. - */ - @Test - public void testSendingSmallFilesAndByteArray() { - String expectedContents = "filecontent: hello"; - String expectedContents2 = "gzipcontent: hello"; - String expectedContents3 = "filecontent: hello2"; - String testResource1 = "textfile.txt"; - String testResource2 = "gzip.txt.gz"; - String testResource3 = "textfile2.txt"; - - File testResource1File = null; - try { - testResource1File = getClasspathFile(testResource1); - } catch (FileNotFoundException e) { - // TODO Auto-generated catch block - fail("unable to find " + testResource1); - } - - File testResource2File = null; - try { - testResource2File = getClasspathFile(testResource2); - } catch (FileNotFoundException e) { - // TODO Auto-generated catch block - fail("unable to find " + testResource2); - } - - File testResource3File = null; - try { - testResource3File = getClasspathFile(testResource3); - } catch (FileNotFoundException e) { - // TODO Auto-generated catch block - fail("unable to find " + testResource3); - } - - List testFiles = new ArrayList(); - testFiles.add(testResource1File); - testFiles.add(testResource2File); - testFiles.add(testResource3File); - - List expected = new ArrayList(); - expected.add(expectedContents); - expected.add(expectedContents2); - expected.add(expectedContents3); - - List gzipped = new ArrayList(); - gzipped.add(false); - gzipped.add(true); - gzipped.add(false); - - boolean tmpFileCreated = false; - File tmpFile = null; - FileOutputStream os = null; - try { - tmpFile = File.createTempFile("textbytearray", ".txt"); - os = new FileOutputStream(tmpFile); - IOUtils.write(expectedContents.getBytes(StandardCharsets.UTF_8), os); - tmpFileCreated = true; - - testFiles.add(tmpFile); - expected.add(expectedContents); - gzipped.add(false); - - } catch (FileNotFoundException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } catch (IOException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } finally { - if (os != null) { - IOUtils.closeQuietly(os); - } - } - - if (!tmpFileCreated) { - fail("Unable to test ByteArrayMultiPart, as unable to write to filesystem the tmp test content"); - } - - AsyncHttpClientConfig.Builder bc = new AsyncHttpClientConfig.Builder(); - - bc.setFollowRedirect(true); - - AsyncHttpClient c = getAsyncHttpClient(bc.build()); - - try { - - RequestBuilder builder = new RequestBuilder("POST"); - builder.setUrl("http://localhost/" + ":" + port1 + "/upload/bob"); - builder.addBodyPart(new FilePart("file1", testResource1File, "text/plain", StandardCharsets.UTF_8)); - builder.addBodyPart(new FilePart("file2", testResource2File, "application/x-gzip", null)); - builder.addBodyPart(new StringPart("Name", "Dominic", StandardCharsets.UTF_8)); - builder.addBodyPart(new FilePart("file3", testResource3File, "text/plain", StandardCharsets.UTF_8)); - - builder.addBodyPart(new StringPart("Age", "3", AsyncHttpProviderUtils.DEFAULT_CHARSET)); - builder.addBodyPart(new StringPart("Height", "shrimplike", AsyncHttpProviderUtils.DEFAULT_CHARSET)); - builder.addBodyPart(new StringPart("Hair", "ridiculous", AsyncHttpProviderUtils.DEFAULT_CHARSET)); - - builder.addBodyPart(new ByteArrayPart("file4", expectedContents.getBytes(StandardCharsets.UTF_8), "text/plain", StandardCharsets.UTF_8, "bytearray.txt")); - - Request r = builder.build(); - - Response res = c.executeRequest(r).get(); - - assertEquals(res.getStatusCode(), 200); - - testSentFile(expected, testFiles, res, gzipped); - - c.close(); - } catch (Exception e) { - e.printStackTrace(); - fail("Download Exception"); - } finally { - c.close(); - FileUtils.deleteQuietly(tmpFile); - } - } - - /** - * Test that the files were sent, based on the response from the servlet - * - * @param expectedContents - * @param sourceFiles - * @param r - * @param deflate - */ - private void testSentFile(List expectedContents, List sourceFiles, Response r, List deflate) { - String content = null; - try { - content = r.getResponseBody(); - assertNotNull("===>" + content); - logger.debug(content); - } catch (IOException e) { - fail("Unable to obtain content"); - } - - String[] contentArray = content.split("\\|\\|"); - // TODO: this fail on win32 - assertEquals(contentArray.length, 2); - - String tmpFiles = contentArray[1]; - assertNotNull(tmpFiles); - assertTrue(tmpFiles.trim().length() > 2); - tmpFiles = tmpFiles.substring(1, tmpFiles.length() - 1); - - String[] responseFiles = tmpFiles.split(","); - assertNotNull(responseFiles); - assertEquals(responseFiles.length, sourceFiles.size()); - - logger.debug(Arrays.toString(responseFiles)); - - int i = 0; - for (File sourceFile : sourceFiles) { - - FileInputStream instream = null; - File tmp = null; - try { - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] sourceBytes = null; - try { - instream = new FileInputStream(sourceFile); - byte[] buf = new byte[8092]; - int len = 0; - while ((len = instream.read(buf)) > 0) { - baos.write(buf, 0, len); - } - logger.debug("================"); - logger.debug("Length of file: " + baos.toByteArray().length); - logger.debug("Contents: " + Arrays.toString(baos.toByteArray())); - logger.debug("================"); - System.out.flush(); - sourceBytes = baos.toByteArray(); - } finally { - IOUtils.closeQuietly(instream); - } - - tmp = new File(responseFiles[i].trim()); - logger.debug("=============================="); - logger.debug(tmp.getAbsolutePath()); - logger.debug("=============================="); - System.out.flush(); - assertTrue(tmp.exists()); - - instream = new FileInputStream(tmp); - ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); - byte[] buf = new byte[8092]; - int len = 0; - while ((len = instream.read(buf)) > 0) { - baos2.write(buf, 0, len); - } - IOUtils.closeQuietly(instream); - - assertEquals(baos2.toByteArray(), sourceBytes); - - if (!deflate.get(i)) { - - String helloString = new String(baos2.toByteArray()); - assertEquals(helloString, expectedContents.get(i)); - } else { - instream = new FileInputStream(tmp); - - ByteArrayOutputStream baos3 = new ByteArrayOutputStream(); - GZIPInputStream deflater = new GZIPInputStream(instream); - try { - byte[] buf3 = new byte[8092]; - int len3 = 0; - while ((len3 = deflater.read(buf3)) > 0) { - baos3.write(buf3, 0, len3); - } - } finally { - deflater.close(); - } - - String helloString = new String(baos3.toByteArray()); - - assertEquals(expectedContents.get(i), helloString); - - } - } catch (Exception e) { - e.printStackTrace(); - fail("Download Exception"); - } finally { - if (tmp != null) - FileUtils.deleteQuietly(tmp); - IOUtils.closeQuietly(instream); - i++; - } - } - } - - /** - * Takes the content that is being passed to it, and streams to a file on disk - * - * @author dominict - */ - public static class MockMultipartUploadServlet extends HttpServlet { - - private static final Logger LOGGER = LoggerFactory.getLogger(MockMultipartUploadServlet.class); - - private static final long serialVersionUID = 1L; - private int filesProcessed = 0; - private int stringsProcessed = 0; - - public MockMultipartUploadServlet() { - - } - - public synchronized void resetFilesProcessed() { - filesProcessed = 0; - } - - private synchronized int incrementFilesProcessed() { - return ++filesProcessed; - - } - - public int getFilesProcessed() { - return filesProcessed; - } - - public synchronized void resetStringsProcessed() { - stringsProcessed = 0; - } - - private synchronized int incrementStringsProcessed() { - return ++stringsProcessed; - - } - - public int getStringsProcessed() { - return stringsProcessed; - } - - @Override - public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - // Check that we have a file upload request - boolean isMultipart = ServletFileUpload.isMultipartContent(request); - if (isMultipart) { - List files = new ArrayList(); - ServletFileUpload upload = new ServletFileUpload(); - // Parse the request - FileItemIterator iter = null; - try { - iter = upload.getItemIterator(request); - while (iter.hasNext()) { - FileItemStream item = iter.next(); - String name = item.getFieldName(); - InputStream stream = null; - try { - stream = item.openStream(); - - if (item.isFormField()) { - LOGGER.debug("Form field " + name + " with value " + Streams.asString(stream) + " detected."); - incrementStringsProcessed(); - } else { - LOGGER.debug("File field " + name + " with file name " + item.getName() + " detected."); - // Process the input stream - OutputStream os = null; - try { - File tmpFile = File.createTempFile(UUID.randomUUID().toString() + "_MockUploadServlet", ".tmp"); - tmpFile.deleteOnExit(); - os = new FileOutputStream(tmpFile); - byte[] buffer = new byte[4096]; - int bytesRead; - while ((bytesRead = stream.read(buffer)) != -1) { - os.write(buffer, 0, bytesRead); - } - incrementFilesProcessed(); - files.add(tmpFile.getAbsolutePath()); - } finally { - IOUtils.closeQuietly(os); - } - } - } finally { - IOUtils.closeQuietly(stream); - } - } - } catch (FileUploadException e) { - - } - Writer w = response.getWriter(); - try { - w.write(Integer.toString(getFilesProcessed())); - resetFilesProcessed(); - resetStringsProcessed(); - w.write("||"); - w.write(files.toString()); - } finally { - // FIXME - w.close(); - } - } else { - Writer w = response.getWriter(); - try { - w.write(Integer.toString(getFilesProcessed())); - resetFilesProcessed(); - resetStringsProcessed(); - w.write("||"); - } finally { - w.close(); - } - } - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/MultipleHeaderTest.java b/api/src/test/java/org/asynchttpclient/async/MultipleHeaderTest.java deleted file mode 100644 index d7c1d99ac6..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/MultipleHeaderTest.java +++ /dev/null @@ -1,206 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * @author Hubert Iwaniuk - */ -public abstract class MultipleHeaderTest extends AbstractBasicTest { - private ExecutorService executorService; - private ServerSocket serverSocket; - private Future voidFuture; - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - - serverSocket = new ServerSocket(port1); - executorService = Executors.newFixedThreadPool(1); - voidFuture = executorService.submit(new Callable() { - public Void call() throws Exception { - Socket socket; - while ((socket = serverSocket.accept()) != null) { - InputStream inputStream = socket.getInputStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - String req = reader.readLine().split(" ")[1]; - int i = inputStream.available(); - long l = inputStream.skip(i); - assertEquals(l, i); - socket.shutdownInput(); - if (req.endsWith("MultiEnt")) { - OutputStreamWriter outputStreamWriter = new OutputStreamWriter(socket.getOutputStream()); - outputStreamWriter.append("HTTP/1.0 200 OK\n" + "Connection: close\n" + "Content-Type: text/plain; charset=iso-8859-1\n" + "Content-Length: 2\n" - + "Content-Length: 1\n" + "\n0\n"); - outputStreamWriter.flush(); - socket.shutdownOutput(); - } else if (req.endsWith("MultiOther")) { - OutputStreamWriter outputStreamWriter = new OutputStreamWriter(socket.getOutputStream()); - outputStreamWriter.append("HTTP/1.0 200 OK\n" + "Connection: close\n" + "Content-Type: text/plain; charset=iso-8859-1\n" + "Content-Length: 1\n" - + "X-Forwarded-For: abc\n" + "X-Forwarded-For: def\n" + "\n0\n"); - outputStreamWriter.flush(); - socket.shutdownOutput(); - } - } - return null; - } - }); - } - - @AfterClass(alwaysRun = true) - public void tearDownGlobal() throws Exception { - voidFuture.cancel(true); - executorService.shutdownNow(); - serverSocket.close(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void testMultipleOtherHeaders() throws IOException, ExecutionException, TimeoutException, InterruptedException { - final String[] xffHeaders = new String[] { null, null }; - - AsyncHttpClient ahc = getAsyncHttpClient(null); - try { - Request req = new RequestBuilder("GET").setUrl("http://localhost/" + port1 + "/MultiOther").build(); - final CountDownLatch latch = new CountDownLatch(1); - ahc.executeRequest(req, new AsyncHandler() { - public void onThrowable(Throwable t) { - t.printStackTrace(System.out); - } - - public STATE onBodyPartReceived(HttpResponseBodyPart objectHttpResponseBodyPart) throws Exception { - return STATE.CONTINUE; - } - - public STATE onStatusReceived(HttpResponseStatus objectHttpResponseStatus) throws Exception { - return STATE.CONTINUE; - } - - public STATE onHeadersReceived(HttpResponseHeaders response) throws Exception { - int i = 0; - for (String header : response.getHeaders().get("X-Forwarded-For")) { - xffHeaders[i++] = header; - } - latch.countDown(); - return STATE.CONTINUE; - } - - public Void onCompleted() throws Exception { - return null; - } - }).get(3, TimeUnit.SECONDS); - - if (!latch.await(2, TimeUnit.SECONDS)) { - fail("Time out"); - } - assertNotNull(xffHeaders[0]); - assertNotNull(xffHeaders[1]); - try { - assertEquals(xffHeaders[0], "abc"); - assertEquals(xffHeaders[1], "def"); - } catch (AssertionError ex) { - assertEquals(xffHeaders[1], "abc"); - assertEquals(xffHeaders[0], "def"); - } - } finally { - ahc.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testMultipleEntityHeaders() throws IOException, ExecutionException, TimeoutException, InterruptedException { - final String[] clHeaders = new String[] { null, null }; - - AsyncHttpClient ahc = getAsyncHttpClient(null); - try { - Request req = new RequestBuilder("GET").setUrl("http://localhost/" + port1 + "/MultiEnt").build(); - final CountDownLatch latch = new CountDownLatch(1); - ahc.executeRequest(req, new AsyncHandler() { - public void onThrowable(Throwable t) { - t.printStackTrace(System.out); - } - - public STATE onBodyPartReceived(HttpResponseBodyPart objectHttpResponseBodyPart) throws Exception { - return STATE.CONTINUE; - } - - public STATE onStatusReceived(HttpResponseStatus objectHttpResponseStatus) throws Exception { - return STATE.CONTINUE; - } - - public STATE onHeadersReceived(HttpResponseHeaders response) throws Exception { - try { - int i = 0; - for (String header : response.getHeaders().get("Content-Length")) { - clHeaders[i++] = header; - } - } finally { - latch.countDown(); - } - return STATE.CONTINUE; - } - - public Void onCompleted() throws Exception { - return null; - } - }).get(3, TimeUnit.SECONDS); - - if (!latch.await(2, TimeUnit.SECONDS)) { - fail("Time out"); - } - assertNotNull(clHeaders[0]); - assertNotNull(clHeaders[1]); - - // We can predict the order - try { - assertEquals(clHeaders[0], "2"); - assertEquals(clHeaders[1], "1"); - } catch (Throwable ex) { - assertEquals(clHeaders[0], "1"); - assertEquals(clHeaders[1], "2"); - } - } finally { - ahc.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java b/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java deleted file mode 100644 index 8ce64afc51..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.BoundRequestBuilder; -import org.asynchttpclient.Response; -import org.testng.annotations.Test; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import java.security.GeneralSecurityException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -public abstract class NoNullResponseTest extends AbstractBasicTest { - private static final String GOOGLE_HTTPS_URL = "https://www.google.com/"; - - @Test(invocationCount = 4, groups = { "online", "default_provider" }) - public void multipleSslRequestsWithDelayAndKeepAlive() throws Exception { - final AsyncHttpClient client = create(); - try { - final BoundRequestBuilder builder = client.prepareGet(GOOGLE_HTTPS_URL); - final Response response1 = builder.execute().get(); - Thread.sleep(4000); - final Response response2 = builder.execute().get(); - if (response2 != null) { - System.out.println("Success (2nd response was not null)."); - } else { - System.out.println("Failed (2nd response was null)."); - } - assertNotNull(response1); - assertNotNull(response2); - } finally { - client.close(); - } - } - - private AsyncHttpClient create() throws GeneralSecurityException { - final AsyncHttpClientConfig.Builder configBuilder = new AsyncHttpClientConfig.Builder().setCompressionEnabled(true).setFollowRedirect(true).setSSLContext(getSSLContext()).setAllowPoolingConnections(true).setConnectionTimeout(10000) - .setPooledConnectionIdleTimeout(60000).setRequestTimeout(10000).setMaxConnectionsPerHost(-1).setMaxConnections(-1); - return getAsyncHttpClient(configBuilder.build()); - } - - private SSLContext getSSLContext() throws GeneralSecurityException { - final SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, new TrustManager[] { new MockTrustManager() }, null); - return sslContext; - } - - private static class MockTrustManager implements X509TrustManager { - public X509Certificate[] getAcceptedIssuers() { - return null; - } - - public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { - // do nothing. - } - - public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { - // Do nothing. - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/NonAsciiContentLengthTest.java b/api/src/test/java/org/asynchttpclient/async/NonAsciiContentLengthTest.java deleted file mode 100644 index b47b52394e..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/NonAsciiContentLengthTest.java +++ /dev/null @@ -1,92 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.BoundRequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.util.StandardCharsets; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -public abstract class NonAsciiContentLengthTest extends AbstractBasicTest { - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - server = newJettyHttpServer(port1); - server.setHandler(new AbstractHandler() { - - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - int MAX_BODY_SIZE = 1024; // Can only handle bodies of up to 1024 bytes. - byte[] b = new byte[MAX_BODY_SIZE]; - int offset = 0; - int numBytesRead; - ServletInputStream is = request.getInputStream(); - try { - while ((numBytesRead = is.read(b, offset, MAX_BODY_SIZE - offset)) != -1) { - offset += numBytesRead; - } - } finally { - is.close(); - } - assertEquals(request.getContentLength(), offset); - response.setStatus(200); - response.setCharacterEncoding(request.getCharacterEncoding()); - response.setContentLength(request.getContentLength()); - ServletOutputStream os = response.getOutputStream(); - try { - os.write(b, 0, offset); - } finally { - os.close(); - } - } - }); - server.start(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void testNonAsciiContentLength() throws Exception { - execute("test"); - execute("\u4E00"); // Unicode CJK ideograph for one - } - - protected void execute(String body) throws IOException, InterruptedException, ExecutionException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - BoundRequestBuilder r = client.preparePost(getTargetUrl()).setBody(body).setBodyEncoding(StandardCharsets.UTF_8.name()); - Future f = r.execute(); - Response resp = f.get(); - assertEquals(resp.getStatusCode(), 200); - assertEquals(body, resp.getResponseBody(StandardCharsets.UTF_8.name())); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/ParamEncodingTest.java b/api/src/test/java/org/asynchttpclient/async/ParamEncodingTest.java deleted file mode 100644 index 4b5027f9a6..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/ParamEncodingTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public abstract class ParamEncodingTest extends AbstractBasicTest { - - private class ParamEncoding extends AbstractHandler { - public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - if ("POST".equalsIgnoreCase(request.getMethod())) { - String p = request.getParameter("test"); - if (isNonEmpty(p)) { - response.setStatus(HttpServletResponse.SC_OK); - response.addHeader("X-Param", p); - } else { - response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE); - } - } else { // this handler is to handle POST request - response.sendError(HttpServletResponse.SC_FORBIDDEN); - } - response.getOutputStream().flush(); - response.getOutputStream().close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testParameters() throws IOException, ExecutionException, TimeoutException, InterruptedException { - - String value = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKQLMNOPQRSTUVWXYZ1234567809`~!@#$%^&*()_+-=,.<>/?;:'\"[]{}\\| "; - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.preparePost("http://127.0.0.1/" + port1).addFormParam("test", value).execute(); - Response resp = f.get(10, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("X-Param"), value.trim()); - } finally { - client.close(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new ParamEncoding(); - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/PerRequestRelative302Test.java b/api/src/test/java/org/asynchttpclient/async/PerRequestRelative302Test.java deleted file mode 100644 index 76450d53c2..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/PerRequestRelative302Test.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; - -import java.io.IOException; -import java.net.ConnectException; -import java.util.Enumeration; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.asynchttpclient.uri.UriComponents; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -public abstract class PerRequestRelative302Test extends AbstractBasicTest { - - private final AtomicBoolean isSet = new AtomicBoolean(false); - - private class Relative302Handler extends AbstractHandler { - - public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { - - String param; - httpResponse.setContentType(TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); - Enumeration e = httpRequest.getHeaderNames(); - while (e.hasMoreElements()) { - param = e.nextElement().toString(); - - if (param.startsWith("X-redirect") && !isSet.getAndSet(true)) { - httpResponse.addHeader("Location", httpRequest.getHeader(param)); - httpResponse.setStatus(302); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - return; - } - } - httpResponse.setStatus(200); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - } - } - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - port2 = findFreePort(); - server = newJettyHttpServer(port1); - - server.setHandler(new Relative302Handler()); - server.start(); - logger.info("Local HTTP server started successfully"); - } - - @Test(groups = { "online", "default_provider" }) - // FIXME threadsafe - public void runAllSequentiallyBecauseNotThreadSafe() throws Exception { - redirected302Test(); - notRedirected302Test(); - relativeLocationUrl(); - redirected302InvalidTest(); - } - - // @Test(groups = { "online", "default_provider" }) - public void redirected302Test() throws Exception { - isSet.getAndSet(false); - AsyncHttpClient c = getAsyncHttpClient(null); - try { - Response response = c.prepareGet(getTargetUrl()).setFollowRedirect(true).setHeader("X-redirect", "http://www.microsoft.com/").execute().get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - - String anyMicrosoftPage = "http://www.microsoft.com[^:]*:80"; - String baseUrl = getBaseUrl(response.getUri()); - - assertTrue(baseUrl.matches(anyMicrosoftPage), "response does not show redirection to " + anyMicrosoftPage); - } finally { - c.close(); - } - } - - // @Test(groups = { "online", "default_provider" }) - public void notRedirected302Test() throws Exception { - isSet.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - try { - Response response = c.prepareGet(getTargetUrl()).setFollowRedirect(false).setHeader("X-redirect", "http://www.microsoft.com/").execute().get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 302); - } finally { - c.close(); - } - } - - private String getBaseUrl(UriComponents uri) { - String url = uri.toString(); - int port = uri.getPort(); - if (port == -1) { - port = getPort(uri); - url = url.substring(0, url.length() - 1) + ":" + port; - } - return url.substring(0, url.lastIndexOf(":") + String.valueOf(port).length() + 1); - } - - private static int getPort(UriComponents uri) { - int port = uri.getPort(); - if (port == -1) - port = uri.getScheme().equals("http") ? 80 : 443; - return port; - } - - // @Test(groups = { "standalone", "default_provider" }) - public void redirected302InvalidTest() throws Exception { - isSet.getAndSet(false); - AsyncHttpClient c = getAsyncHttpClient(null); - try { - // If the test hit a proxy, no ConnectException will be thrown and instead of 404 will be returned. - Response response = c.preparePost(getTargetUrl()).setFollowRedirect(true).setHeader("X-redirect", String.format("http://127.0.0.1:%d/", port2)).execute().get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 404); - } catch (ExecutionException ex) { - assertEquals(ex.getCause().getClass(), ConnectException.class); - } finally { - c.close(); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void relativeLocationUrl() throws Exception { - isSet.getAndSet(false); - - AsyncHttpClient c = getAsyncHttpClient(null); - try { - Response response = c.preparePost(getTargetUrl()).setFollowRedirect(true).setHeader("X-redirect", "/foo/test").execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 302); - assertEquals(response.getUri().toString(), getTargetUrl()); - } finally { - c.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/PerRequestTimeoutTest.java b/api/src/test/java/org/asynchttpclient/async/PerRequestTimeoutTest.java deleted file mode 100644 index 9063a8f87e..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/PerRequestTimeoutTest.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.util.DateUtils.millisTime; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.Response; -import org.eclipse.jetty.continuation.Continuation; -import org.eclipse.jetty.continuation.ContinuationSupport; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * Per request timeout configuration test. - * - * @author Hubert Iwaniuk - */ -public abstract class PerRequestTimeoutTest extends AbstractBasicTest { - private static final String MSG = "Enough is enough."; - - protected abstract void checkTimeoutMessage(String message); - - @Override - public AbstractHandler configureHandler() throws Exception { - return new SlowHandler(); - } - - private class SlowHandler extends AbstractHandler { - public void handle(String target, Request baseRequest, HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException { - response.setStatus(HttpServletResponse.SC_OK); - final Continuation continuation = ContinuationSupport.getContinuation(request); - continuation.suspend(); - new Thread(new Runnable() { - public void run() { - try { - Thread.sleep(1500); - response.getOutputStream().print(MSG); - response.getOutputStream().flush(); - } catch (InterruptedException e) { - logger.error(e.getMessage(), e); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - }).start(); - new Thread(new Runnable() { - public void run() { - try { - Thread.sleep(3000); - response.getOutputStream().print(MSG); - response.getOutputStream().flush(); - continuation.complete(); - } catch (InterruptedException e) { - logger.error(e.getMessage(), e); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - }).start(); - baseRequest.setHandled(true); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testRequestTimeout() throws IOException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future responseFuture = client.prepareGet(getTargetUrl()).setRequestTimeoutInMs(100).execute(); - Response response = responseFuture.get(2000, TimeUnit.MILLISECONDS); - assertNull(response); - } catch (InterruptedException e) { - fail("Interrupted.", e); - } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof TimeoutException); - checkTimeoutMessage(e.getCause().getMessage()); - } catch (TimeoutException e) { - fail("Timeout.", e); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testGlobalDefaultPerRequestInfiniteTimeout() throws IOException { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(100).build()); - try { - Future responseFuture = client.prepareGet(getTargetUrl()).setRequestTimeoutInMs(-1).execute(); - Response response = responseFuture.get(); - assertNotNull(response); - } catch (InterruptedException e) { - fail("Interrupted.", e); - } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof TimeoutException); - checkTimeoutMessage(e.getCause().getMessage()); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testGlobalRequestTimeout() throws IOException { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(100).build()); - try { - Future responseFuture = client.prepareGet(getTargetUrl()).execute(); - Response response = responseFuture.get(2000, TimeUnit.MILLISECONDS); - assertNull(response); - } catch (InterruptedException e) { - fail("Interrupted.", e); - } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof TimeoutException); - checkTimeoutMessage(e.getCause().getMessage()); - } catch (TimeoutException e) { - fail("Timeout.", e); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testGlobalIdleTimeout() throws IOException { - final long times[] = new long[] { -1, -1 }; - - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setPooledConnectionIdleTimeout(2000).build()); - try { - Future responseFuture = client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandler() { - @Override - public Response onCompleted(Response response) throws Exception { - return response; - } - - @Override - public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { - times[0] = millisTime(); - return super.onBodyPartReceived(content); - } - - @Override - public void onThrowable(Throwable t) { - times[1] = millisTime(); - super.onThrowable(t); - } - }); - Response response = responseFuture.get(); - assertNotNull(response); - assertEquals(response.getResponseBody(), MSG + MSG); - } catch (InterruptedException e) { - fail("Interrupted.", e); - } catch (ExecutionException e) { - logger.info(String.format("\n@%dms Last body part received\n@%dms Connection killed\n %dms difference.", times[0], times[1], (times[1] - times[0]))); - fail("Timeouted on idle.", e); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java b/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java deleted file mode 100644 index 306db3efed..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.ResponseFilter; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicInteger; - -public abstract class PostRedirectGetTest extends AbstractBasicTest { - - // ------------------------------------------------------ Test Configuration - - @Override - public AbstractHandler configureHandler() throws Exception { - return new PostRedirectGetHandler(); - } - - // ------------------------------------------------------------ Test Methods - - @Test(groups = { "standalone", "post_redirect_get" }) - public void postRedirectGet302Test() throws Exception { - doTestPositive(302); - } - - @Test(groups = { "standalone", "post_redirect_get" }) - public void postRedirectGet302StrictTest() throws Exception { - doTestNegative(302, true); - } - - @Test(groups = { "standalone", "post_redirect_get" }) - public void postRedirectGet303Test() throws Exception { - doTestPositive(303); - } - - @Test(groups = { "standalone", "post_redirect_get" }) - public void postRedirectGet301Test() throws Exception { - doTestNegative(301, false); - } - - @Test(groups = { "standalone", "post_redirect_get" }) - public void postRedirectGet307Test() throws Exception { - doTestNegative(307, false); - } - - // --------------------------------------------------------- Private Methods - - private void doTestNegative(final int status, boolean strict) throws Exception { - AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).setStrict302Handling(strict).addResponseFilter(new ResponseFilter() { - @Override - public FilterContext filter(FilterContext ctx) throws FilterException { - // pass on the x-expect-get and remove the x-redirect - // headers if found in the response - ctx.getResponseHeaders().getHeaders().get("x-expect-post"); - ctx.getRequest().getHeaders().add("x-expect-post", "true"); - ctx.getRequest().getHeaders().remove("x-redirect"); - return ctx; - } - }).build()); - try { - Request request = new RequestBuilder("POST").setUrl(getTargetUrl()).addFormParam("q", "a b").addHeader("x-redirect", +status + "@" + "http://localhost/" + port1 + "/foo/bar/baz").addHeader("x-negative", "true").build(); - Future responseFuture = p.executeRequest(request, new AsyncCompletionHandler() { - - @Override - public Integer onCompleted(Response response) throws Exception { - return response.getStatusCode(); - } - - @Override - public void onThrowable(Throwable t) { - t.printStackTrace(); - fail("Unexpected exception: " + t.getMessage(), t); - } - - }); - int statusCode = responseFuture.get(); - assertEquals(statusCode, 200); - } finally { - p.close(); - } - } - - private void doTestPositive(final int status) throws Exception { - AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).addResponseFilter(new ResponseFilter() { - @Override - public FilterContext filter(FilterContext ctx) throws FilterException { - // pass on the x-expect-get and remove the x-redirect - // headers if found in the response - ctx.getResponseHeaders().getHeaders().get("x-expect-get"); - ctx.getRequest().getHeaders().add("x-expect-get", "true"); - ctx.getRequest().getHeaders().remove("x-redirect"); - return ctx; - } - }).build()); - try { - Request request = new RequestBuilder("POST").setUrl(getTargetUrl()).addFormParam("q", "a b").addHeader("x-redirect", +status + "@" + "http://localhost/" + port1 + "/foo/bar/baz").build(); - Future responseFuture = p.executeRequest(request, new AsyncCompletionHandler() { - - @Override - public Integer onCompleted(Response response) throws Exception { - return response.getStatusCode(); - } - - @Override - public void onThrowable(Throwable t) { - t.printStackTrace(); - fail("Unexpected exception: " + t.getMessage(), t); - } - - }); - int statusCode = responseFuture.get(); - assertEquals(statusCode, 200); - } finally { - p.close(); - } - } - - // ---------------------------------------------------------- Nested Classes - - public static class PostRedirectGetHandler extends AbstractHandler { - - final AtomicInteger counter = new AtomicInteger(); - - @Override - public void handle(String pathInContext, org.eclipse.jetty.server.Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { - - final boolean expectGet = (httpRequest.getHeader("x-expect-get") != null); - final boolean expectPost = (httpRequest.getHeader("x-expect-post") != null); - if (expectGet) { - final String method = request.getMethod(); - if (!"GET".equals(method)) { - httpResponse.sendError(500, "Incorrect method. Expected GET, received " + method); - return; - } - httpResponse.setStatus(200); - httpResponse.getOutputStream().write("OK".getBytes()); - httpResponse.getOutputStream().flush(); - return; - } else if (expectPost) { - final String method = request.getMethod(); - if (!"POST".equals(method)) { - httpResponse.sendError(500, "Incorrect method. Expected POST, received " + method); - return; - } - httpResponse.setStatus(200); - httpResponse.getOutputStream().write("OK".getBytes()); - httpResponse.getOutputStream().flush(); - return; - } - - String header = httpRequest.getHeader("x-redirect"); - if (header != null) { - // format for header is | - String[] parts = header.split("@"); - int redirectCode; - try { - redirectCode = Integer.parseInt(parts[0]); - } catch (Exception ex) { - ex.printStackTrace(); - httpResponse.sendError(500, "Unable to parse redirect code"); - return; - } - httpResponse.setStatus(redirectCode); - if (httpRequest.getHeader("x-negative") == null) { - httpResponse.addHeader("x-expect-get", "true"); - } else { - httpResponse.addHeader("x-expect-post", "true"); - } - httpResponse.setContentLength(0); - httpResponse.addHeader("Location", parts[1] + counter.getAndIncrement()); - httpResponse.getOutputStream().flush(); - return; - } - - httpResponse.sendError(500); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java b/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java deleted file mode 100644 index f5bb672154..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncCompletionHandlerBase; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.ServletInputStream; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * Tests POST request with Query String. - * - * @author Hubert Iwaniuk - */ -public abstract class PostWithQSTest extends AbstractBasicTest { - - /** - * POST with QS server part. - */ - private class PostWithQSHandler extends AbstractHandler { - public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - if ("POST".equalsIgnoreCase(request.getMethod())) { - String qs = request.getQueryString(); - if (isNonEmpty(qs) && request.getContentLength() == 3) { - ServletInputStream is = request.getInputStream(); - response.setStatus(HttpServletResponse.SC_OK); - byte buf[] = new byte[is.available()]; - is.readLine(buf, 0, is.available()); - ServletOutputStream os = response.getOutputStream(); - os.println(new String(buf)); - os.flush(); - os.close(); - } else { - response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE); - } - } else { // this handler is to handle POST request - response.sendError(HttpServletResponse.SC_FORBIDDEN); - } - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void postWithQS() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.preparePost("http://127.0.0.1/" + port1 + "/?a=b").setBody("abc".getBytes()).execute(); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void postWithNulParamQS() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.preparePost("http://127.0.0.1/" + port1 + "/?a=").setBody("abc".getBytes()).execute(new AsyncCompletionHandlerBase() { - - @Override - public STATE onStatusReceived(final HttpResponseStatus status) throws Exception { - if (!status.getUri().toUrl().equals("http://127.0.0.1/" + port1 + "/?a=")) { - throw new IOException(status.getUri().toUrl()); - } - return super.onStatusReceived(status); - } - - }); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void postWithNulParamsQS() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.preparePost("http://127.0.0.1/" + port1 + "/?a=b&c&d=e").setBody("abc".getBytes()).execute(new AsyncCompletionHandlerBase() { - - @Override - public STATE onStatusReceived(final HttpResponseStatus status) throws Exception { - if (!status.getUri().toUrl().equals("http://127.0.0.1/" + port1 + "/?a=b&c&d=e")) { - throw new IOException("failed to parse the query properly"); - } - return super.onStatusReceived(status); - } - - }); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void postWithEmptyParamsQS() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.preparePost("http://127.0.0.1/" + port1 + "/?a=b&c=&d=e").setBody("abc".getBytes()).execute(new AsyncCompletionHandlerBase() { - - @Override - public STATE onStatusReceived(final HttpResponseStatus status) throws Exception { - if (!status.getUri().toUrl().equals("http://127.0.0.1/" + port1 + "/?a=b&c=&d=e")) { - throw new IOException("failed to parse the query properly"); - } - return super.onStatusReceived(status); - } - - }); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - } finally { - client.close(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new PostWithQSHandler(); - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/ProxyTest.java b/api/src/test/java/org/asynchttpclient/async/ProxyTest.java deleted file mode 100644 index 3e5220d696..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/ProxyTest.java +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.net.ConnectException; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.ProxySelector; -import java.net.SocketAddress; -import java.net.URI; -import java.util.Arrays; -import java.util.List; -import java.util.Properties; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * Proxy usage tests. - * - * @author Hubert Iwaniuk - */ -public abstract class ProxyTest extends AbstractBasicTest { - private class ProxyHandler extends AbstractHandler { - public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - if ("GET".equalsIgnoreCase(request.getMethod())) { - response.addHeader("target", r.getUri().getPath()); - response.setStatus(HttpServletResponse.SC_OK); - } else { - // this handler is to handle POST request - response.sendError(HttpServletResponse.SC_FORBIDDEN); - } - r.setHandled(true); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new ProxyHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void testRequestLevelProxy() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - String target = "http://127.0.0.1:1234/"; - Future f = client.prepareGet(target).setProxyServer(new ProxyServer("127.0.0.1", port1)).execute(); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("target"), "/"); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testGlobalProxy() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClientConfig cfg = new AsyncHttpClientConfig.Builder().setProxyServer(new ProxyServer("127.0.0.1", port1)).build(); - AsyncHttpClient client = getAsyncHttpClient(cfg); - try { - String target = "http://127.0.0.1:1234/"; - Future f = client.prepareGet(target).execute(); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("target"), "/"); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testBothProxies() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClientConfig cfg = new AsyncHttpClientConfig.Builder().setProxyServer(new ProxyServer("127.0.0.1", port1 - 1)).build(); - AsyncHttpClient client = getAsyncHttpClient(cfg); - try { - String target = "http://127.0.0.1:1234/"; - Future f = client.prepareGet(target).setProxyServer(new ProxyServer("127.0.0.1", port1)).execute(); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("target"), "/"); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testNonProxyHosts() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClientConfig cfg = new AsyncHttpClientConfig.Builder().setProxyServer(new ProxyServer("127.0.0.1", port1 - 1)).build(); - AsyncHttpClient client = getAsyncHttpClient(cfg); - try { - - String target = "http://127.0.0.1:1234/"; - client.prepareGet(target).setProxyServer(new ProxyServer("127.0.0.1", port1).addNonProxyHost("127.0.0.1")).execute().get(); - assertFalse(true); - } catch (Throwable e) { - assertNotNull(e.getCause()); - assertEquals(e.getCause().getClass(), ConnectException.class); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testNonProxyHostIssue202() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - String target = "http://127.0.0.1/" + port1 + "/"; - Future f = client.prepareGet(target).setProxyServer(new ProxyServer("127.0.0.1", port1 - 1).addNonProxyHost("127.0.0.1")).execute(); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("target"), "/"); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void runSequentiallyBecauseNotThreadSafe() throws Exception { - testProxyProperties(); - testIgnoreProxyPropertiesByDefault(); - testProxyActivationProperty(); - testWildcardNonProxyHosts(); - testUseProxySelector(); - } - - // @Test(groups = { "standalone", "default_provider" }) - public void testProxyProperties() throws IOException, ExecutionException, TimeoutException, InterruptedException { - Properties originalProps = System.getProperties(); - try { - Properties props = new Properties(); - props.putAll(originalProps); - - // FIXME most likely non threadsafe! - System.setProperties(props); - - System.setProperty("http.proxyHost", "127.0.0.1"); - System.setProperty("http.proxyPort", String.valueOf(port1)); - System.setProperty("http.nonProxyHosts", "localhost"); - - AsyncHttpClientConfig cfg = new AsyncHttpClientConfig.Builder().setUseProxyProperties(true).build(); - AsyncHttpClient client = getAsyncHttpClient(cfg); - try { - String target = "http://127.0.0.1:1234/"; - Future f = client.prepareGet(target).execute(); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("target"), "/"); - - target = "http://localhost:1234/"; - f = client.prepareGet(target).execute(); - try { - resp = f.get(3, TimeUnit.SECONDS); - fail("should not be able to connect"); - } catch (ExecutionException e) { - // ok, no proxy used - } - } finally { - client.close(); - } - } finally { - System.setProperties(originalProps); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void testIgnoreProxyPropertiesByDefault() throws IOException, ExecutionException, TimeoutException, InterruptedException { - Properties originalProps = System.getProperties(); - try { - Properties props = new Properties(); - props.putAll(originalProps); - - // FIXME not threadsafe! - System.setProperties(props); - - System.setProperty("http.proxyHost", "127.0.0.1"); - System.setProperty("http.proxyPort", String.valueOf(port1)); - System.setProperty("http.nonProxyHosts", "localhost"); - - AsyncHttpClient client = getAsyncHttpClient(null); - try { - String target = "http://127.0.0.1:1234/"; - Future f = client.prepareGet(target).execute(); - try { - f.get(3, TimeUnit.SECONDS); - fail("should not be able to connect"); - } catch (ExecutionException e) { - // ok, no proxy used - } - } finally { - client.close(); - } - } finally { - System.setProperties(originalProps); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void testProxyActivationProperty() throws IOException, ExecutionException, TimeoutException, InterruptedException { - Properties originalProps = System.getProperties(); - try { - Properties props = new Properties(); - props.putAll(originalProps); - - // FIXME not threadsafe! - System.setProperties(props); - - System.setProperty("http.proxyHost", "127.0.0.1"); - System.setProperty("http.proxyPort", String.valueOf(port1)); - System.setProperty("http.nonProxyHosts", "localhost"); - System.setProperty("org.asynchttpclient.AsyncHttpClientConfig.useProxyProperties", "true"); - - AsyncHttpClient client = getAsyncHttpClient(null); - try { - String target = "http://127.0.0.1:1234/"; - Future f = client.prepareGet(target).execute(); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("target"), "/"); - - target = "http://localhost:1234/"; - f = client.prepareGet(target).execute(); - try { - resp = f.get(3, TimeUnit.SECONDS); - fail("should not be able to connect"); - } catch (ExecutionException e) { - // ok, no proxy used - } - } finally { - client.close(); - } - } finally { - System.setProperties(originalProps); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void testWildcardNonProxyHosts() throws IOException, ExecutionException, TimeoutException, InterruptedException { - Properties originalProps = System.getProperties(); - try { - Properties props = new Properties(); - props.putAll(originalProps); - - // FIXME not threadsafe! - System.setProperties(props); - - System.setProperty("http.proxyHost", "127.0.0.1"); - System.setProperty("http.proxyPort", String.valueOf(port1)); - System.setProperty("http.nonProxyHosts", "127.*"); - - AsyncHttpClientConfig cfg = new AsyncHttpClientConfig.Builder().setUseProxyProperties(true).build(); - AsyncHttpClient client = getAsyncHttpClient(cfg); - try { - String target = "http://127.0.0.1:1234/"; - Future f = client.prepareGet(target).execute(); - try { - f.get(3, TimeUnit.SECONDS); - fail("should not be able to connect"); - } catch (ExecutionException e) { - // ok, no proxy used - } - } finally { - client.close(); - } - } finally { - System.setProperties(originalProps); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void testUseProxySelector() throws IOException, ExecutionException, TimeoutException, InterruptedException { - ProxySelector originalProxySelector = ProxySelector.getDefault(); - try { - ProxySelector.setDefault(new ProxySelector() { - public List select(URI uri) { - if (uri.getHost().equals("127.0.0.1")) { - return Arrays.asList(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", port1))); - } else { - return Arrays.asList(Proxy.NO_PROXY); - } - } - - public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { - } - }); - - AsyncHttpClientConfig cfg = new AsyncHttpClientConfig.Builder().setUseProxySelector(true).build(); - AsyncHttpClient client = getAsyncHttpClient(cfg); - try { - String target = "http://127.0.0.1:1234/"; - Future f = client.prepareGet(target).execute(); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("target"), "/"); - - target = "http://localhost:1234/"; - f = client.prepareGet(target).execute(); - try { - f.get(3, TimeUnit.SECONDS); - fail("should not be able to connect"); - } catch (ExecutionException e) { - // ok, no proxy used - } - } finally { - client.close(); - } - } finally { - // FIXME not threadsafe - ProxySelector.setDefault(originalProxySelector); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/ProxyTunnellingTest.java b/api/src/test/java/org/asynchttpclient/async/ProxyTunnellingTest.java deleted file mode 100644 index 203f52a26b..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/ProxyTunnellingTest.java +++ /dev/null @@ -1,159 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpsServer; -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncCompletionHandlerBase; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.SimpleAsyncHttpClient; -import org.asynchttpclient.async.util.EchoHandler; -import org.eclipse.jetty.proxy.ConnectHandler; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeoutException; - -/** - * Proxy usage tests. - */ -public abstract class ProxyTunnellingTest extends AbstractBasicTest { - - private Server server2; - - public abstract String getProviderClass(); - - public AbstractHandler configureHandler() throws Exception { - return new ConnectHandler(); - } - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - server = newJettyHttpServer(port1); - server.setHandler(configureHandler()); - server.start(); - - port2 = findFreePort(); - - server2 = newJettyHttpsServer(port2); - server2.setHandler(new EchoHandler()); - server2.start(); - - logger.info("Local HTTP server started successfully"); - } - - @AfterClass(alwaysRun = true) - public void tearDownGlobal() throws Exception { - server.stop(); - server2.stop(); - } - - @Test(groups = { "online", "default_provider" }) - public void testRequestProxy() throws IOException, InterruptedException, ExecutionException, TimeoutException { - - ProxyServer ps = new ProxyServer(ProxyServer.Protocol.HTTPS, "127.0.0.1", port1); - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setFollowRedirect(true)// - .setAcceptAnyCertificate(true)// - .build(); - - AsyncHttpClient asyncHttpClient = getAsyncHttpClient(config); - try { - RequestBuilder rb = new RequestBuilder("GET").setProxyServer(ps).setUrl(getTargetUrl2()); - Future responseFuture = asyncHttpClient.executeRequest(rb.build(), new AsyncCompletionHandlerBase() { - - public void onThrowable(Throwable t) { - t.printStackTrace(); - logger.debug(t.getMessage(), t); - } - - @Override - public Response onCompleted(Response response) throws Exception { - return response; - } - }); - Response r = responseFuture.get(); - assertEquals(r.getStatusCode(), 200); - assertEquals(r.getHeader("X-Connection"), "keep-alive"); - } finally { - asyncHttpClient.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void testConfigProxy() throws IOException, InterruptedException, ExecutionException, TimeoutException { - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setFollowRedirect(true)// - .setProxyServer(new ProxyServer(ProxyServer.Protocol.HTTPS, "127.0.0.1", port1))// - .setAcceptAnyCertificate(true)// - .build(); - AsyncHttpClient asyncHttpClient = getAsyncHttpClient(config); - try { - Future responseFuture = asyncHttpClient.executeRequest(new RequestBuilder("GET").setUrl(getTargetUrl2()).build(), new AsyncCompletionHandlerBase() { - - public void onThrowable(Throwable t) { - t.printStackTrace(); - logger.debug(t.getMessage(), t); - } - - @Override - public Response onCompleted(Response response) throws Exception { - return response; - } - }); - Response r = responseFuture.get(); - assertEquals(r.getStatusCode(), 200); - assertEquals(r.getHeader("X-Connection"), "keep-alive"); - } finally { - asyncHttpClient.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void testSimpleAHCConfigProxy() throws IOException, InterruptedException, ExecutionException, TimeoutException { - - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder()// - .setProviderClass(getProviderClass())// - .setProxyProtocol(ProxyServer.Protocol.HTTPS)// - .setProxyHost("127.0.0.1")// - .setProxyPort(port1)// - .setFollowRedirect(true)// - .setUrl(getTargetUrl2())// - .setAcceptAnyCertificate(true)// - .setHeader("Content-Type", "text/html")// - .build(); - try { - Response r = client.get().get(); - - assertEquals(r.getStatusCode(), 200); - assertEquals(r.getHeader("X-Connection"), "keep-alive"); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/PutLargeFileTest.java b/api/src/test/java/org/asynchttpclient/async/PutLargeFileTest.java deleted file mode 100644 index 51400e3040..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/PutLargeFileTest.java +++ /dev/null @@ -1,81 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.createTempFile; -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.File; -import java.io.IOException; - -/** - * @author Benjamin Hanzelmann - */ -public abstract class PutLargeFileTest extends AbstractBasicTest { - - @Test(groups = { "standalone", "default_provider" }, enabled = true) - public void testPutLargeFile() throws Exception { - - File file = createTempFile(1024 * 1024); - - int timeout = (int) file.length() / 1000; - - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeout(timeout).build()); - try { - Response response = client.preparePut(getTargetUrl()).setBody(file).execute().get(); - assertEquals(response.getStatusCode(), 200); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testPutSmallFile() throws Exception { - - File file = createTempFile(1024); - - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.preparePut(getTargetUrl()).setBody(file).execute().get(); - assertEquals(response.getStatusCode(), 200); - } finally { - client.close(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new AbstractHandler() { - - public void handle(String arg0, Request arg1, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - - resp.setStatus(200); - resp.getOutputStream().flush(); - resp.getOutputStream().close(); - - arg1.setHandled(true); - } - }; - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/QueryParametersTest.java b/api/src/test/java/org/asynchttpclient/async/QueryParametersTest.java deleted file mode 100644 index 6bbd6c80ac..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/QueryParametersTest.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.Response; -import org.asynchttpclient.util.StandardCharsets; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.slf4j.LoggerFactory; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * Testing query parameters support. - * - * @author Hubert Iwaniuk - */ -public abstract class QueryParametersTest extends AbstractBasicTest { - private class QueryStringHandler extends AbstractHandler { - public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - if ("GET".equalsIgnoreCase(request.getMethod())) { - String qs = request.getQueryString(); - if (isNonEmpty(qs)) { - for (String qnv : qs.split("&")) { - String nv[] = qnv.split("="); - response.addHeader(nv[0], nv[1]); - } - response.setStatus(HttpServletResponse.SC_OK); - } else { - response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE); - } - } else { // this handler is to handle POST request - response.sendError(HttpServletResponse.SC_FORBIDDEN); - } - r.setHandled(true); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new QueryStringHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void testQueryParameters() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.prepareGet("http://127.0.0.1/" + port1).addQueryParam("a", "1").addQueryParam("b", "2").execute(); - Response resp = f.get(3, TimeUnit.SECONDS); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getHeader("a"), "1"); - assertEquals(resp.getHeader("b"), "2"); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testUrlRequestParametersEncoding() throws IOException, ExecutionException, InterruptedException { - String URL = getTargetUrl() + "?q="; - String REQUEST_PARAM = "github github \ngithub"; - - AsyncHttpClient client = getAsyncHttpClient(null); - try { - String requestUrl2 = URL + URLEncoder.encode(REQUEST_PARAM, StandardCharsets.UTF_8.name()); - LoggerFactory.getLogger(QueryParametersTest.class).info("Executing request [{}] ...", requestUrl2); - Response response = client.prepareGet(requestUrl2).execute().get(); - String s = URLDecoder.decode(response.getHeader("q"), StandardCharsets.UTF_8.name()); - assertEquals(s, REQUEST_PARAM); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void urlWithColonTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - String query = "test:colon:"; - Response response = c.prepareGet(String.format("http://127.0.0.1:%d/foo/test/colon?q=%s", port1, query)).setHeader("Content-Type", "text/html").execute().get(TIMEOUT, TimeUnit.SECONDS); - - assertEquals(response.getHeader("q"), URLEncoder.encode(query, StandardCharsets.UTF_8.name())); - } finally { - c.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/RC10KTest.java b/api/src/test/java/org/asynchttpclient/async/RC10KTest.java deleted file mode 100644 index e3a1993058..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/RC10KTest.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Reverse C10K Problem test. - * - * @author Hubert Iwaniuk - */ -public abstract class RC10KTest extends AbstractBasicTest { - private static final int C10K = 1000; - private static final String ARG_HEADER = "Arg"; - private static final int SRV_COUNT = 10; - protected List servers = new ArrayList(SRV_COUNT); - private int[] ports; - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - ports = new int[SRV_COUNT]; - for (int i = 0; i < SRV_COUNT; i++) { - ports[i] = createServer(); - } - logger.info("Local HTTP servers started successfully"); - } - - @AfterClass(alwaysRun = true) - public void tearDownGlobal() throws Exception { - for (Server srv : servers) { - srv.stop(); - } - } - - private int createServer() throws Exception { - int port = findFreePort(); - Server srv = newJettyHttpServer(port); - srv.setHandler(configureHandler()); - srv.start(); - servers.add(srv); - return port; - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new AbstractHandler() { - public void handle(String s, Request r, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - resp.setContentType("text/pain"); - String arg = s.substring(1); - resp.setHeader(ARG_HEADER, arg); - resp.setStatus(200); - resp.getOutputStream().print(arg); - resp.getOutputStream().flush(); - resp.getOutputStream().close(); - } - }; - } - - @Test(timeOut = 10 * 60 * 1000, groups = "scalability") - public void rc10kProblem() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient ahc = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setMaxConnectionsPerHost(C10K).setAllowPoolingConnections(true).build()); - try { - List> resps = new ArrayList>(C10K); - int i = 0; - while (i < C10K) { - resps.add(ahc.prepareGet(String.format("http://127.0.0.1:%d/%d", ports[i % SRV_COUNT], i)).execute(new MyAsyncHandler(i++))); - } - i = 0; - for (Future fResp : resps) { - Integer resp = fResp.get(); - assertNotNull(resp); - assertEquals(resp.intValue(), i++); - } - } finally { - ahc.close(); - } - } - - private class MyAsyncHandler implements AsyncHandler { - private String arg; - private AtomicInteger result = new AtomicInteger(-1); - - public MyAsyncHandler(int i) { - arg = String.format("%d", i); - } - - public void onThrowable(Throwable t) { - logger.warn("onThrowable called.", t); - } - - public STATE onBodyPartReceived(HttpResponseBodyPart event) throws Exception { - String s = new String(event.getBodyPartBytes()); - result.compareAndSet(-1, new Integer(s.trim().equals("") ? "-1" : s)); - return STATE.CONTINUE; - } - - public STATE onStatusReceived(HttpResponseStatus event) throws Exception { - assertEquals(event.getStatusCode(), 200); - return STATE.CONTINUE; - } - - public STATE onHeadersReceived(HttpResponseHeaders event) throws Exception { - assertEquals(event.getHeaders().getJoinedValue(ARG_HEADER, ", "), arg); - return STATE.CONTINUE; - } - - public Integer onCompleted() throws Exception { - return result.get(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java b/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java deleted file mode 100644 index 26bfbfc2f9..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.Date; - -/** - * Test for multithreaded url fetcher calls that use two separate sets of ssl certificates. This then tests that the certificate settings do not clash (override each other), - * resulting in the peer not authenticated exception - * - * @author dominict - */ -public abstract class RedirectConnectionUsageTest extends AbstractBasicTest { - private String BASE_URL; - - private String servletEndpointRedirectUrl; - - @BeforeClass - public void setUp() throws Exception { - port1 = findFreePort(); - server = newJettyHttpServer(port1); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.addServlet(new ServletHolder(new MockRedirectHttpServlet()), "/redirect/*"); - context.addServlet(new ServletHolder(new MockFullResponseHttpServlet()), "/*"); - server.setHandler(context); - - server.start(); - - BASE_URL = "http://localhost/" + ":" + port1; - servletEndpointRedirectUrl = BASE_URL + "/redirect"; - } - - /** - * Tests that after a redirect the final url in the response reflect the redirect - */ - @Test - public void testGetRedirectFinalUrl() throws Exception { - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setAllowPoolingConnections(true)// - .setMaxConnectionsPerHost(1)// - .setMaxConnections(1)// - .setConnectionTimeout(1000)// - .setRequestTimeout(1000)// - .setFollowRedirect(true)// - .build(); - - AsyncHttpClient c = getAsyncHttpClient(config); - try { - Request r = new RequestBuilder("GET").setUrl(servletEndpointRedirectUrl).build(); - - ListenableFuture response = c.executeRequest(r); - Response res = null; - res = response.get(); - assertNotNull(res.getResponseBody()); - assertEquals(res.getUri().toString(), BASE_URL + "/overthere"); - - } finally { - c.close(); - } - } - - @SuppressWarnings("serial") - class MockRedirectHttpServlet extends HttpServlet { - public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { - res.sendRedirect("/overthere"); - } - } - - @SuppressWarnings("serial") - class MockFullResponseHttpServlet extends HttpServlet { - - private static final String contentType = "text/xml"; - private static final String xml = ""; - - public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { - String xmlToReturn = String.format(xml, new Object[] { new Date().toString() }); - - res.setStatus(200); - res.addHeader("Content-Type", contentType); - res.addHeader("X-Method", req.getMethod()); - res.addHeader("MultiValue", "1"); - res.addHeader("MultiValue", "2"); - res.addHeader("MultiValue", "3"); - - OutputStream os = res.getOutputStream(); - - byte[] retVal = xmlToReturn.getBytes(); - res.setContentLength(retVal.length); - os.write(retVal); - os.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/Relative302Test.java b/api/src/test/java/org/asynchttpclient/async/Relative302Test.java deleted file mode 100644 index 081ddd1e0a..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/Relative302Test.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.asynchttpclient.async.util.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; - -import java.io.IOException; -import java.net.ConnectException; -import java.net.URI; -import java.util.Enumeration; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.asynchttpclient.uri.UriComponents; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -public abstract class Relative302Test extends AbstractBasicTest { - private final AtomicBoolean isSet = new AtomicBoolean(false); - - private class Relative302Handler extends AbstractHandler { - - public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { - - String param; - httpResponse.setContentType(TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); - Enumeration e = httpRequest.getHeaderNames(); - while (e.hasMoreElements()) { - param = e.nextElement().toString(); - - if (param.startsWith("X-redirect") && !isSet.getAndSet(true)) { - httpResponse.addHeader("Location", httpRequest.getHeader(param)); - httpResponse.setStatus(302); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - return; - } - } - httpResponse.setStatus(200); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - } - } - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - port2 = findFreePort(); - server = newJettyHttpServer(port1); - server.setHandler(new Relative302Handler()); - server.start(); - logger.info("Local HTTP server started successfully"); - } - - @Test(groups = { "online", "default_provider" }) - public void testAllSequentiallyBecauseNotThreadSafe() throws Exception { - redirected302Test(); - redirected302InvalidTest(); - absolutePathRedirectTest(); - relativePathRedirectTest(); - } - - // @Test(groups = { "online", "default_provider" }) - public void redirected302Test() throws Exception { - isSet.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - - try { - Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", "http://www.google.com/").execute().get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - - String baseUrl = getBaseUrl(response.getUri()); - - assertTrue(baseUrl.startsWith("http://www.google./"), "response does not show redirection to a google subdomain, got " + baseUrl); - } finally { - c.close(); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void redirected302InvalidTest() throws Exception { - isSet.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - - // If the test hit a proxy, no ConnectException will be thrown and instead of 404 will be returned. - try { - Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", String.format("http://127.0.0.1:%d/", port2)).execute().get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 404); - } catch (ExecutionException ex) { - assertEquals(ex.getCause().getClass(), ConnectException.class); - } finally { - c.close(); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void absolutePathRedirectTest() throws Exception { - isSet.getAndSet(false); - - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - try { - String redirectTarget = "/bar/test"; - String destinationUrl = new URI(getTargetUrl()).resolve(redirectTarget).toString(); - - Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", redirectTarget).execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getUri().toString(), destinationUrl); - - logger.debug("{} was redirected to {}", redirectTarget, destinationUrl); - } finally { - c.close(); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void relativePathRedirectTest() throws Exception { - isSet.getAndSet(false); - - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - try { - String redirectTarget = "bar/test1"; - String destinationUrl = new URI(getTargetUrl()).resolve(redirectTarget).toString(); - - Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", redirectTarget).execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getUri().toString(), destinationUrl); - - logger.debug("{} was redirected to {}", redirectTarget, destinationUrl); - } finally { - c.close(); - } - } - - private String getBaseUrl(UriComponents uri) { - String url = uri.toString(); - int port = uri.getPort(); - if (port == -1) { - port = getPort(uri); - url = url.substring(0, url.length() - 1) + ":" + port; - } - return url.substring(0, url.lastIndexOf(":") + String.valueOf(port).length() + 1); - } - - private static int getPort(UriComponents uri) { - int port = uri.getPort(); - if (port == -1) - port = uri.getScheme().equals("http") ? 80 : 443; - return port; - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java b/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java deleted file mode 100644 index 7e8ad5f21c..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.apache.commons.io.IOUtils; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.util.StandardCharsets; -import org.testng.annotations.Test; - -import java.io.InputStream; -import java.net.URLEncoder; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * Unit tests for remote site. - *

- * see http://github.com/MSch/ning-async-http-client-bug/tree/master - * - * @author Martin Schurrer - */ -public abstract class RemoteSiteTest extends AbstractBasicTest { - - public static final String URL = "http://google.com/?q="; - public static final String REQUEST_PARAM = "github github \n" + "github"; - - @Test(groups = { "online", "default_provider" }) - public void testGoogleCom() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build()); - try { - Response response = c.prepareGet("http://www.google.com/").execute().get(10, TimeUnit.SECONDS); - assertNotNull(response); - } finally { - c.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void testMailGoogleCom() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build()); - try { - Response response = c.prepareGet("http://mail.google.com/").execute().get(10, TimeUnit.SECONDS); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - } finally { - c.close(); - } - } - - @Test(groups = { "online", "default_provider" }, enabled = false) - // FIXME - public void testMicrosoftCom() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build()); - try { - Response response = c.prepareGet("http://microsoft.com/").execute().get(10, TimeUnit.SECONDS); - assertNotNull(response); - assertEquals(response.getStatusCode(), 301); - } finally { - c.close(); - } - } - - @Test(groups = { "online", "default_provider" }, enabled = false) - // FIXME - public void testWwwMicrosoftCom() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build()); - try { - Response response = c.prepareGet("http://www.microsoft.com/").execute().get(10, TimeUnit.SECONDS); - assertNotNull(response); - assertEquals(response.getStatusCode(), 302); - } finally { - c.close(); - } - } - - @Test(groups = { "online", "default_provider" }, enabled = false) - // FIXME - public void testUpdateMicrosoftCom() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build()); - try { - Response response = c.prepareGet("http://update.microsoft.com/").execute().get(10, TimeUnit.SECONDS); - assertNotNull(response); - assertEquals(response.getStatusCode(), 302); - } finally { - c.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void testGoogleComWithTimeout() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build()); - try { - Response response = c.prepareGet("http://google.com/").execute().get(10, TimeUnit.SECONDS); - assertNotNull(response); - assertTrue(response.getStatusCode() == 301 || response.getStatusCode() == 302); - } finally { - c.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void asyncStatusHEADContentLenghtTest() throws Exception { - AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build()); - try { - final CountDownLatch l = new CountDownLatch(1); - Request request = new RequestBuilder("HEAD").setUrl("http://www.google.com/").build(); - - p.executeRequest(request, new AsyncCompletionHandlerAdapter() { - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - return response; - } finally { - l.countDown(); - } - } - }).get(); - - if (!l.await(5, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - p.close(); - } - } - - @Test(groups = { "online", "default_provider" }, enabled = false) - public void invalidStreamTest2() throws Exception { - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).setFollowRedirect(true) - .setAllowPoolingConnections(false).setMaxRedirects(6).build(); - - AsyncHttpClient c = getAsyncHttpClient(config); - try { - Response response = c.prepareGet("http://bit.ly/aUjTtG").execute().get(); - if (response != null) { - System.out.println(response); - } - } catch (Throwable t) { - t.printStackTrace(); - assertNotNull(t.getCause()); - assertEquals(t.getCause().getMessage(), "invalid version format: ICY"); - } finally { - c.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void asyncFullBodyProperlyRead() throws Exception { - final AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response r = client.prepareGet("http://www.cyberpresse.ca/").execute().get(); - - InputStream stream = r.getResponseBodyAsStream(); - int contentLength = Integer.valueOf(r.getHeader("Content-Length")); - - assertEquals(contentLength, IOUtils.toByteArray(stream).length); - } finally { - client.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void testUrlRequestParametersEncoding() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - String requestUrl2 = URL + URLEncoder.encode(REQUEST_PARAM, StandardCharsets.UTF_8.name()); - logger.info(String.format("Executing request [%s] ...", requestUrl2)); - Response response = client.prepareGet(requestUrl2).execute().get(); - assertEquals(response.getStatusCode(), 301); - } finally { - client.close(); - } - } - - /** - * See https://issues.sonatype.org/browse/AHC-61 - * - * @throws Exception - */ - @Test(groups = { "online", "default_provider" }) - public void testAHC60() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Response response = client.prepareGet("http://www.meetup.com/stackoverflow/Mountain-View-CA/").execute().get(); - assertEquals(response.getStatusCode(), 200); - } finally { - client.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void stripQueryStringTest() throws Exception { - - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - try { - Response response = c.prepareGet("http://www.freakonomics.com/?p=55846").execute().get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - } finally { - c.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void stripQueryStringNegativeTest() throws Exception { - - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setRemoveQueryParamsOnRedirect(false).setFollowRedirect(true) - .build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - try { - Response response = c.prepareGet("http://www.freakonomics.com/?p=55846").execute().get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 301); - } finally { - c.close(); - } - } - - @Test(groups = { "online", "default_provider" }) - public void evilCoookieTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - RequestBuilder builder2 = new RequestBuilder("GET"); - builder2.setFollowRedirect(true); - builder2.setUrl("http://www.google.com/"); - builder2.addHeader("Content-Type", "text/plain"); - builder2.addCookie(new Cookie("evilcookie", "test", "test", ".google.com", "/", -1L, 10, false, false)); - Request request2 = builder2.build(); - Response response = c.executeRequest(request2).get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - } finally { - c.close(); - } - } - - @Test(groups = { "online", "default_provider" }, enabled = false) - public void testAHC62Com() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build()); - try { - Response response = c.prepareGet("http://api.crunchbase.com/v/1/financial-organization/kinsey-hills-group.js") - .execute(new AsyncHandler() { - - private Response.ResponseBuilder builder = new Response.ResponseBuilder(); - - public void onThrowable(Throwable t) { - t.printStackTrace(); - } - - public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - System.out.println(bodyPart.getBodyPartBytes().length); - builder.accumulate(bodyPart); - - return STATE.CONTINUE; - } - - public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { - builder.accumulate(responseStatus); - return STATE.CONTINUE; - } - - public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { - builder.accumulate(headers); - return STATE.CONTINUE; - } - - public Response onCompleted() throws Exception { - return builder.build(); - } - }).get(10, TimeUnit.SECONDS); - assertNotNull(response); - assertTrue(response.getResponseBody().length() >= 3870); - } finally { - c.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java b/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java deleted file mode 100644 index 9086595c99..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.async; - -import static org.testng.Assert.assertEquals; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.List; -import java.util.concurrent.ExecutionException; - -import org.asynchttpclient.Param; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.testng.annotations.Test; - -public class RequestBuilderTest { - - private final static String SAFE_CHARS = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-_~."; - private final static String HEX_CHARS = "0123456789ABCDEF"; - - @Test(groups = {"standalone", "default_provider"}) - public void testEncodesQueryParameters() throws UnsupportedEncodingException { - String[] values = new String[]{ - "abcdefghijklmnopqrstuvwxyz", - "ABCDEFGHIJKQLMNOPQRSTUVWXYZ", - "1234567890", "1234567890", - "`~!@#$%^&*()", "`~!@#$%^&*()", - "_+-=,.<>/?", "_+-=,.<>/?", - ";:'\"[]{}\\| ", ";:'\"[]{}\\| " - }; - - /* as per RFC-5849 (Oauth), and RFC-3986 (percent encoding) we MUST - * encode everything except for "safe" characters; and nothing but them. - * Safe includes ascii letters (upper and lower case), digits (0 - 9) - * and FOUR special characters: hyphen ('-'), underscore ('_'), - * tilde ('~') and period ('.')). Everything else must be percent-encoded, - * byte-by-byte, using UTF-8 encoding (meaning three-byte Unicode/UTF-8 - * code points are encoded as three three-letter percent-encode entities). - */ - for (String value : values) { - RequestBuilder builder = new RequestBuilder("GET"). - setUrl("http://example.com/"). - addQueryParam("name", value); - - StringBuilder sb = new StringBuilder(); - for (int i = 0, len = value.length(); i < len; ++i) { - char c = value.charAt(i); - if (SAFE_CHARS.indexOf(c) >= 0) { - sb.append(c); - } else { - int hi = (c >> 4); - int lo = c & 0xF; - sb.append('%').append(HEX_CHARS.charAt(hi)).append(HEX_CHARS.charAt(lo)); - } - } - String expValue = sb.toString(); - Request request = builder.build(); - assertEquals(request.getURI().toUrl(), "http://example.com/?name=" + expValue); - } - } - - @Test(groups = {"standalone", "default_provider"}) - public void testChaining() throws IOException, ExecutionException, InterruptedException { - Request request = new RequestBuilder("GET") - .setUrl("http://foo.com/") - .addQueryParam("x", "value") - .build(); - - Request request2 = new RequestBuilder(request).build(); - - assertEquals(request2.getURI(), request.getURI()); - } - - @Test(groups = {"standalone", "default_provider"}) - public void testParsesQueryParams() throws IOException, ExecutionException, InterruptedException { - Request request = new RequestBuilder("GET") - .setUrl("http://foo.com/?param1=value1") - .addQueryParam("param2", "value2") - .build(); - - assertEquals(request.getURI().toUrl(), "http://foo.com/?param1=value1¶m2=value2"); - List params = request.getQueryParams(); - assertEquals(params.size(), 2); - assertEquals(params.get(0), new Param("param1", "value1")); - assertEquals(params.get(1), new Param("param2", "value2")); - } - - @Test(groups = {"standalone", "default_provider"}) - public void testUserProvidedRequestMethod() { - Request req = new RequestBuilder("ABC").setUrl("http://foo.com/").build(); - assertEquals(req.getMethod(), "ABC"); - assertEquals(req.getURI().toUrl(), "http://foo.com/"); - } - - @Test(groups = {"standalone", "default_provider"}) - public void testPercentageEncodedUserInfo() { - final Request req = new RequestBuilder("GET").setUrl("http://hello:wor%20ld@foo.com/").build(); - assertEquals(req.getMethod(), "GET"); - assertEquals(req.getURI().toUrl(), "http://hello:wor%20ld@foo.com/"); - } - - @Test(groups = {"standalone", "default_provider"}) - public void testContentTypeCharsetToBodyEncoding() { - final Request req = new RequestBuilder("GET").setHeader("Content-Type", "application/json; charset=utf-8").build(); - assertEquals(req.getBodyEncoding(), "utf-8"); - final Request req2 = new RequestBuilder("GET").setHeader("Content-Type", "application/json; charset=\"utf-8\"").build(); - assertEquals(req2.getBodyEncoding(), "utf-8"); - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/RetryRequestTest.java b/api/src/test/java/org/asynchttpclient/async/RetryRequestTest.java deleted file mode 100644 index 8706232a26..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/RetryRequestTest.java +++ /dev/null @@ -1,90 +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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.io.OutputStream; - -public abstract class RetryRequestTest extends AbstractBasicTest { - public static class SlowAndBigHandler extends AbstractHandler { - - public void handle(String pathInContext, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { - - int load = 100; - httpResponse.setStatus(200); - httpResponse.setContentLength(load); - httpResponse.setContentType("application/octet-stream"); - - httpResponse.flushBuffer(); - - OutputStream os = httpResponse.getOutputStream(); - for (int i = 0; i < load; i++) { - os.write(i % 255); - - try { - Thread.sleep(300); - } catch (InterruptedException ex) { - // nuku - } - - if (i > load / 10) { - httpResponse.sendError(500); - } - } - - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - } - } - - protected String getTargetUrl() { - return String.format("http://127.0.0.1:%d/", port1); - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new SlowAndBigHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void testMaxRetry() throws Exception { - AsyncHttpClient ahc = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setMaxRequestRetry(0).build()); - try { - ahc.executeRequest(ahc.prepareGet(getTargetUrl()).build()).get(); - fail(); - } catch (Exception t) { - assertNotNull(t.getCause()); - assertEquals(t.getCause().getClass(), IOException.class); - if (t.getCause() != AsyncHttpProviderUtils.REMOTELY_CLOSED_EXCEPTION) { - fail(); - } - } finally { - ahc.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncClientErrorBehaviourTest.java b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncClientErrorBehaviourTest.java deleted file mode 100644 index 5912a7079a..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncClientErrorBehaviourTest.java +++ /dev/null @@ -1,86 +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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.Response; -import org.asynchttpclient.SimpleAsyncHttpClient; -import org.asynchttpclient.SimpleAsyncHttpClient.ErrorDocumentBehaviour; -import org.asynchttpclient.consumers.OutputStreamBodyConsumer; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.concurrent.Future; - -/** - * @author Benjamin Hanzelmann - * - */ -public abstract class SimpleAsyncClientErrorBehaviourTest extends AbstractBasicTest { - - public abstract String getProviderClass(); - - @Test(groups = { "standalone", "default_provider" }) - public void testAccumulateErrorBody() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl() + "/nonexistent").setErrorDocumentBehaviour(ErrorDocumentBehaviour.ACCUMULATE).build(); - try { - ByteArrayOutputStream o = new ByteArrayOutputStream(10); - Future future = client.get(new OutputStreamBodyConsumer(o)); - - System.out.println("waiting for response"); - Response response = future.get(); - assertEquals(response.getStatusCode(), 404); - assertEquals(o.toString(), ""); - assertTrue(response.getResponseBody().startsWith("")); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testOmitErrorBody() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl() + "/nonexistent").setErrorDocumentBehaviour(ErrorDocumentBehaviour.OMIT).build(); - try { - ByteArrayOutputStream o = new ByteArrayOutputStream(10); - Future future = client.get(new OutputStreamBodyConsumer(o)); - - System.out.println("waiting for response"); - Response response = future.get(); - assertEquals(response.getStatusCode(), 404); - assertEquals(o.toString(), ""); - assertEquals(response.getResponseBody(), ""); - } finally { - client.close(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new AbstractHandler() { - - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - response.sendError(404); - baseRequest.setHandled(true); - } - }; - } - -} diff --git a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java deleted file mode 100644 index 59b4caa6ae..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java +++ /dev/null @@ -1,353 +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.async; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNotSame; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.Response; -import org.asynchttpclient.SimpleAsyncHttpClient; -import org.asynchttpclient.consumers.AppendableBodyConsumer; -import org.asynchttpclient.consumers.OutputStreamBodyConsumer; -import org.asynchttpclient.generators.FileBodyGenerator; -import org.asynchttpclient.generators.InputStreamBodyGenerator; -import org.asynchttpclient.multipart.ByteArrayPart; -import org.asynchttpclient.simple.HeaderMap; -import org.asynchttpclient.simple.SimpleAHCTransferListener; -import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.StandardCharsets; -import org.testng.annotations.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Future; - -public abstract class SimpleAsyncHttpClientTest extends AbstractBasicTest { - - private final static String MY_MESSAGE = "my message"; - - public abstract String getProviderClass(); - - @Test(groups = { "standalone", "default_provider" }) - public void inpuStreamBodyConsumerTest() throws Exception { - - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setPooledConnectionIdleTimeout(100) - .setMaxConnections(50).setRequestTimeout(5 * 60 * 1000).setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); - try { - Future future = client.post(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes()))); - - Response response = future.get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getResponseBody(), MY_MESSAGE); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void stringBuilderBodyConsumerTest() throws Exception { - - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setPooledConnectionIdleTimeout(100) - .setMaxConnections(50).setRequestTimeout(5 * 60 * 1000).setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); - try { - StringBuilder s = new StringBuilder(); - Future future = client.post(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes())), new AppendableBodyConsumer(s)); - - Response response = future.get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(s.toString(), MY_MESSAGE); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void byteArrayOutputStreamBodyConsumerTest() throws Exception { - - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setPooledConnectionIdleTimeout(100) - .setMaxConnections(50).setRequestTimeout(5 * 60 * 1000).setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); - try { - ByteArrayOutputStream o = new ByteArrayOutputStream(10); - Future future = client.post(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes())), new OutputStreamBodyConsumer(o)); - - Response response = future.get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(o.toString(), MY_MESSAGE); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void requestByteArrayOutputStreamBodyConsumerTest() throws Exception { - - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl()).build(); - try { - ByteArrayOutputStream o = new ByteArrayOutputStream(10); - Future future = client.post(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes())), new OutputStreamBodyConsumer(o)); - - Response response = future.get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(o.toString(), MY_MESSAGE); - } finally { - client.close(); - } - } - - /** - * See https://issues.sonatype.org/browse/AHC-5 - */ - @Test(groups = { "standalone", "default_provider" }, enabled = true) - public void testPutZeroBytesFileTest() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setPooledConnectionIdleTimeout(100) - .setMaxConnections(50).setRequestTimeout(5 * 1000).setUrl(getTargetUrl() + "/testPutZeroBytesFileTest.txt").setHeader("Content-Type", "text/plain") - .build(); - try { - File tmpfile = File.createTempFile("testPutZeroBytesFile", ".tmp"); - tmpfile.deleteOnExit(); - - Future future = client.put(new FileBodyGenerator(tmpfile)); - - System.out.println("waiting for response"); - Response response = future.get(); - - tmpfile.delete(); - - assertEquals(response.getStatusCode(), 200); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testDerive() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).build(); - SimpleAsyncHttpClient derived = client.derive().build(); - try { - assertNotSame(derived, client); - } finally { - client.close(); - derived.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testDeriveOverrideURL() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl("http://invalid.url/").build(); - ByteArrayOutputStream o = new ByteArrayOutputStream(10); - - InputStreamBodyGenerator generator = new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes())); - OutputStreamBodyConsumer consumer = new OutputStreamBodyConsumer(o); - - SimpleAsyncHttpClient derived = client.derive().setUrl(getTargetUrl()).build(); - try { - Future future = derived.post(generator, consumer); - - Response response = future.get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(o.toString(), MY_MESSAGE); - } finally { - client.close(); - derived.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testSimpleTransferListener() throws Exception { - - final List errors = Collections.synchronizedList(new ArrayList()); - - SimpleAHCTransferListener listener = new SimpleAHCTransferListener() { - - public void onStatus(UriComponents uri, int statusCode, String statusText) { - try { - assertEquals(statusCode, 200); - assertEquals(uri.toUrl(), getTargetUrl()); - } catch (Error e) { - errors.add(e); - throw e; - } - } - - public void onHeaders(UriComponents uri, HeaderMap headers) { - try { - assertEquals(uri.toUrl(), getTargetUrl()); - assertNotNull(headers); - assertTrue(!headers.isEmpty()); - assertEquals(headers.getFirstValue("X-Custom"), "custom"); - } catch (Error e) { - errors.add(e); - throw e; - } - } - - public void onCompleted(UriComponents uri, int statusCode, String statusText) { - try { - assertEquals(statusCode, 200); - assertEquals(uri.toUrl(), getTargetUrl()); - } catch (Error e) { - errors.add(e); - throw e; - } - } - - public void onBytesSent(UriComponents uri, long amount, long current, long total) { - try { - assertEquals(uri.toUrl(), getTargetUrl()); - // FIXME Netty bug, see https://github.com/netty/netty/issues/1855 -// assertEquals(total, MY_MESSAGE.getBytes().length); - } catch (Error e) { - errors.add(e); - throw e; - } - } - - public void onBytesReceived(UriComponents uri, long amount, long current, long total) { - try { - assertEquals(uri.toUrl(), getTargetUrl()); - assertEquals(total, -1); - } catch (Error e) { - errors.add(e); - throw e; - } - } - }; - - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl()).setHeader("Custom", "custom") - .setListener(listener).build(); - try { - ByteArrayOutputStream o = new ByteArrayOutputStream(10); - - InputStreamBodyGenerator generator = new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes())); - OutputStreamBodyConsumer consumer = new OutputStreamBodyConsumer(o); - - Future future = client.post(generator, consumer); - - Response response = future.get(); - - if (!errors.isEmpty()) { - for (Error e : errors) { - e.printStackTrace(); - } - throw errors.get(0); - } - - assertEquals(response.getStatusCode(), 200); - assertEquals(o.toString(), MY_MESSAGE); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testNullUrl() throws Exception { - SimpleAsyncHttpClient client = null; - try { - client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).build(); - assertTrue(true); - } catch (NullPointerException ex) { - fail(); - } finally { - if (client != null) - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testCloseDerivedValidMaster() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl()).build(); - SimpleAsyncHttpClient derived = client.derive().build(); - try { - derived.get().get(); - - derived.close(); - - Response response = client.get().get(); - - assertEquals(response.getStatusCode(), 200); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testCloseMasterInvalidDerived() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl()).build(); - SimpleAsyncHttpClient derived = client.derive().build(); - - client.close(); - - try { - derived.get().get(); - fail("Expected closed AHC"); - } catch (IOException e) { - // expected - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testMultiPartPut() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl() + "/multipart").build(); - try { - Response response = client.put(new ByteArrayPart("baPart", "testMultiPart".getBytes(StandardCharsets.UTF_8), "application/test", StandardCharsets.UTF_8, "fileName")).get(); - - String body = response.getResponseBody(); - String contentType = response.getHeader("X-Content-Type"); - - assertTrue(contentType.contains("multipart/form-data")); - - String boundary = contentType.substring(contentType.lastIndexOf("=") + 1); - - assertTrue(body.startsWith("--" + boundary)); - assertTrue(body.trim().endsWith("--" + boundary + "--")); - assertTrue(body.contains("Content-Disposition:")); - assertTrue(body.contains("Content-Type: application/test")); - assertTrue(body.contains("name=\"baPart")); - assertTrue(body.contains("filename=\"fileName")); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testMultiPartPost() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl() + "/multipart").build(); - try { - Response response = client.post(new ByteArrayPart("baPart", "testMultiPart".getBytes(StandardCharsets.UTF_8), "application/test", StandardCharsets.UTF_8, "fileName")).get(); - - String body = response.getResponseBody(); - String contentType = response.getHeader("X-Content-Type"); - - assertTrue(contentType.contains("multipart/form-data")); - - String boundary = contentType.substring(contentType.lastIndexOf("=") + 1); - - assertTrue(body.startsWith("--" + boundary)); - assertTrue(body.trim().endsWith("--" + boundary + "--")); - assertTrue(body.contains("Content-Disposition:")); - assertTrue(body.contains("Content-Type: application/test")); - assertTrue(body.contains("name=\"baPart")); - assertTrue(body.contains("filename=\"fileName")); - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/TransferListenerTest.java b/api/src/test/java/org/asynchttpclient/async/TransferListenerTest.java deleted file mode 100644 index 90ffb19938..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/TransferListenerTest.java +++ /dev/null @@ -1,256 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.createTempFile; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.Response; -import org.asynchttpclient.generators.FileBodyGenerator; -import org.asynchttpclient.listener.TransferCompletionHandler; -import org.asynchttpclient.listener.TransferListener; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.File; -import java.io.IOException; -import java.util.Enumeration; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; - -public abstract class TransferListenerTest extends AbstractBasicTest { - - private class BasicHandler extends AbstractHandler { - - public void handle(String s, org.eclipse.jetty.server.Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { - - Enumeration e = httpRequest.getHeaderNames(); - String param; - while (e.hasMoreElements()) { - param = e.nextElement().toString(); - httpResponse.addHeader("X-" + param, httpRequest.getHeader(param)); - } - - int size = 10 * 1024; - if (httpRequest.getContentLength() > 0) { - size = httpRequest.getContentLength(); - } - byte[] bytes = new byte[size]; - if (bytes.length > 0) { - int read = 0; - while (read != -1) { - read = httpRequest.getInputStream().read(bytes); - if (read > 0) { - httpResponse.getOutputStream().write(bytes, 0, read); - } - } - } - - httpResponse.setStatus(200); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new BasicHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicGetTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final AtomicReference throwable = new AtomicReference(); - final AtomicReference hSent = new AtomicReference(); - final AtomicReference hRead = new AtomicReference(); - final AtomicReference bb = new AtomicReference(); - final AtomicBoolean completed = new AtomicBoolean(false); - - TransferCompletionHandler tl = new TransferCompletionHandler(); - tl.addTransferListener(new TransferListener() { - - public void onRequestHeadersSent(FluentCaseInsensitiveStringsMap headers) { - hSent.set(headers); - } - - public void onResponseHeadersReceived(FluentCaseInsensitiveStringsMap headers) { - hRead.set(headers); - } - - public void onBytesReceived(byte[] b) { - if (b.length != 0) - bb.set(b); - } - - public void onBytesSent(long amount, long current, long total) { - } - - public void onRequestResponseCompleted() { - completed.set(true); - } - - public void onThrowable(Throwable t) { - throwable.set(t); - } - }); - - try { - Response response = c.prepareGet(getTargetUrl()).execute(tl).get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertNotNull(hRead.get()); - assertNotNull(hSent.get()); - assertNull(bb.get()); - assertNull(throwable.get()); - } catch (IOException ex) { - fail("Should have timed out"); - } - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicPutFileTest() throws Exception { - final AtomicReference throwable = new AtomicReference(); - final AtomicReference hSent = new AtomicReference(); - final AtomicReference hRead = new AtomicReference(); - final AtomicInteger bbReceivedLenght = new AtomicInteger(0); - final AtomicLong bbSentLenght = new AtomicLong(0L); - - final AtomicBoolean completed = new AtomicBoolean(false); - - File file = createTempFile(1024 * 100 * 10); - - int timeout = (int) (file.length() / 1000); - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeout(timeout).build()); - - try { - TransferCompletionHandler tl = new TransferCompletionHandler(); - tl.addTransferListener(new TransferListener() { - - public void onRequestHeadersSent(FluentCaseInsensitiveStringsMap headers) { - hSent.set(headers); - } - - public void onResponseHeadersReceived(FluentCaseInsensitiveStringsMap headers) { - hRead.set(headers); - } - - public void onBytesReceived(byte[] b) { - bbReceivedLenght.addAndGet(b.length); - } - - public void onBytesSent(long amount, long current, long total) { - bbSentLenght.addAndGet(amount); - } - - public void onRequestResponseCompleted() { - completed.set(true); - } - - public void onThrowable(Throwable t) { - throwable.set(t); - } - }); - - try { - Response response = client.preparePut(getTargetUrl()).setBody(file).execute(tl).get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertNotNull(hRead.get()); - assertNotNull(hSent.get()); - assertEquals(bbReceivedLenght.get(), file.length(), "Number of received bytes incorrect"); - assertEquals(bbSentLenght.get(), file.length(), "Number of sent bytes incorrect"); - } catch (IOException ex) { - fail("Should have timed out"); - } - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicPutFileBodyGeneratorTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final AtomicReference throwable = new AtomicReference(); - final AtomicReference hSent = new AtomicReference(); - final AtomicReference hRead = new AtomicReference(); - final AtomicInteger bbReceivedLenght = new AtomicInteger(0); - final AtomicLong bbSentLenght = new AtomicLong(0L); - - final AtomicBoolean completed = new AtomicBoolean(false); - - File file = createTempFile(1024 * 100 * 10); - - TransferCompletionHandler tl = new TransferCompletionHandler(); - tl.addTransferListener(new TransferListener() { - - public void onRequestHeadersSent(FluentCaseInsensitiveStringsMap headers) { - hSent.set(headers); - } - - public void onResponseHeadersReceived(FluentCaseInsensitiveStringsMap headers) { - hRead.set(headers); - } - - public void onBytesReceived(byte[] b) { - bbReceivedLenght.addAndGet(b.length); - } - - public void onBytesSent(long amount, long current, long total) { - bbSentLenght.addAndGet(amount); - } - - public void onRequestResponseCompleted() { - completed.set(true); - } - - public void onThrowable(Throwable t) { - throwable.set(t); - } - }); - - try { - Response response = client.preparePut(getTargetUrl()).setBody(new FileBodyGenerator(file)).execute(tl).get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertNotNull(hRead.get()); - assertNotNull(hSent.get()); - assertEquals(bbReceivedLenght.get(), file.length(), "Number of received bytes incorrect"); - assertEquals(bbSentLenght.get(), file.length(), "Number of sent bytes incorrect"); - } catch (IOException ex) { - fail("Should have timed out"); - } - } finally { - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/WebDavBasicTest.java b/api/src/test/java/org/asynchttpclient/async/WebDavBasicTest.java deleted file mode 100644 index 7045925d5e..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/WebDavBasicTest.java +++ /dev/null @@ -1,197 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; - -import org.apache.catalina.Context; -import org.apache.catalina.Engine; -import org.apache.catalina.Host; -import org.apache.catalina.Wrapper; -import org.apache.catalina.connector.Connector; -import org.apache.catalina.startup.Embedded; -import org.apache.coyote.http11.Http11NioProtocol; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.webdav.WebDavCompletionHandlerBase; -import org.asynchttpclient.webdav.WebDavResponse; -import org.testng.annotations.AfterClass; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import java.io.File; -import java.io.IOException; -import java.util.concurrent.ExecutionException; - -public abstract class WebDavBasicTest extends AbstractBasicTest { - - protected Embedded embedded; - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - - port1 = findFreePort(); - embedded = new Embedded(); - String path = new File(".").getAbsolutePath(); - embedded.setCatalinaHome(path); - - Engine engine = embedded.createEngine(); - engine.setDefaultHost("127.0.0.1"); - - Host host = embedded.createHost("127.0.0.1", path); - engine.addChild(host); - - Context c = embedded.createContext("/", path); - c.setReloadable(false); - Wrapper w = c.createWrapper(); - w.addMapping("/*"); - w.setServletClass(org.apache.catalina.servlets.WebdavServlet.class.getName()); - w.addInitParameter("readonly", "false"); - w.addInitParameter("listings", "true"); - - w.setLoadOnStartup(0); - - c.addChild(w); - host.addChild(c); - - Connector connector = embedded.createConnector("127.0.0.1", port1, Http11NioProtocol.class.getName()); - connector.setContainer(host); - embedded.addEngine(engine); - embedded.addConnector(connector); - embedded.start(); - } - - @AfterClass(alwaysRun = true) - public void tearDownGlobal() throws InterruptedException, Exception { - embedded.stop(); - } - - protected String getTargetUrl() { - return String.format("http://127.0.0.1:%s/folder1", port1); - } - - @AfterMethod(alwaysRun = true) - // FIXME not sure that's threadsafe - public void clean() throws InterruptedException, Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - Request deleteRequest = new RequestBuilder("DELETE").setUrl(getTargetUrl()).build(); - c.executeRequest(deleteRequest).get(); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void mkcolWebDavTest1() throws InterruptedException, IOException, ExecutionException { - - AsyncHttpClient c = getAsyncHttpClient(null); - try { - Request mkcolRequest = new RequestBuilder("MKCOL").setUrl(getTargetUrl()).build(); - Response response = c.executeRequest(mkcolRequest).get(); - - assertEquals(response.getStatusCode(), 201); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void mkcolWebDavTest2() throws InterruptedException, IOException, ExecutionException { - - AsyncHttpClient c = getAsyncHttpClient(null); - try { - Request mkcolRequest = new RequestBuilder("MKCOL").setUrl(getTargetUrl() + "/folder2").build(); - Response response = c.executeRequest(mkcolRequest).get(); - assertEquals(response.getStatusCode(), 409); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicPropFindWebDavTest() throws InterruptedException, IOException, ExecutionException { - - AsyncHttpClient c = getAsyncHttpClient(null); - try { - Request propFindRequest = new RequestBuilder("PROPFIND").setUrl(getTargetUrl()).build(); - Response response = c.executeRequest(propFindRequest).get(); - - assertEquals(response.getStatusCode(), 404); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void propFindWebDavTest() throws InterruptedException, IOException, ExecutionException { - - AsyncHttpClient c = getAsyncHttpClient(null); - try { - Request mkcolRequest = new RequestBuilder("MKCOL").setUrl(getTargetUrl()).build(); - Response response = c.executeRequest(mkcolRequest).get(); - assertEquals(response.getStatusCode(), 201); - - Request putRequest = new RequestBuilder("PUT").setUrl(String.format("http://127.0.0.1:%s/folder1/Test.txt", port1)).setBody("this is a test").build(); - response = c.executeRequest(putRequest).get(); - assertEquals(response.getStatusCode(), 201); - - Request propFindRequest = new RequestBuilder("PROPFIND").setUrl(String.format("http://127.0.0.1:%s/folder1/Test.txt", port1)).build(); - response = c.executeRequest(propFindRequest).get(); - - assertEquals(response.getStatusCode(), 207); - assertTrue(response.getResponseBody().contains("HTTP/1.1 200 OK")); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void propFindCompletionHandlerWebDavTest() throws InterruptedException, IOException, ExecutionException { - - AsyncHttpClient c = getAsyncHttpClient(null); - try { - Request mkcolRequest = new RequestBuilder("MKCOL").setUrl(getTargetUrl()).build(); - Response response = c.executeRequest(mkcolRequest).get(); - assertEquals(response.getStatusCode(), 201); - - Request propFindRequest = new RequestBuilder("PROPFIND").setUrl(getTargetUrl()).build(); - WebDavResponse webDavResponse = c.executeRequest(propFindRequest, new WebDavCompletionHandlerBase() { - /** - * {@inheritDoc} - */ - @Override - public void onThrowable(Throwable t) { - - t.printStackTrace(); - } - - @Override - public WebDavResponse onCompleted(WebDavResponse response) throws Exception { - return response; - } - }).get(); - - assertNotNull(webDavResponse); - assertEquals(webDavResponse.getStatusCode(), 200); - } finally { - c.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/ZeroCopyFileTest.java b/api/src/test/java/org/asynchttpclient/async/ZeroCopyFileTest.java deleted file mode 100644 index 0ef54128fc..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/ZeroCopyFileTest.java +++ /dev/null @@ -1,199 +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.async; - -import static org.asynchttpclient.async.util.TestUtils.SIMPLE_TEXT_FILE; -import static org.asynchttpclient.async.util.TestUtils.SIMPLE_TEXT_FILE_STRING; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Zero copy test which use FileChannel.transfer under the hood . The same SSL test is also covered in {@link BasicHttpsTest} - */ -public abstract class ZeroCopyFileTest extends AbstractBasicTest { - - private class ZeroCopyHandler extends AbstractHandler { - public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { - - int size = 10 * 1024; - if (httpRequest.getContentLength() > 0) { - size = httpRequest.getContentLength(); - } - byte[] bytes = new byte[size]; - if (bytes.length > 0) { - httpRequest.getInputStream().read(bytes); - httpResponse.getOutputStream().write(bytes); - } - - httpResponse.setStatus(200); - httpResponse.getOutputStream().flush(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void zeroCopyPostTest() throws IOException, ExecutionException, TimeoutException, InterruptedException, URISyntaxException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - final AtomicBoolean headerSent = new AtomicBoolean(false); - final AtomicBoolean operationCompleted = new AtomicBoolean(false); - - Response resp = client.preparePost("http://127.0.0.1/" + port1 + "/").setBody(SIMPLE_TEXT_FILE).execute(new AsyncCompletionHandler() { - - public STATE onHeaderWriteCompleted() { - headerSent.set(true); - return STATE.CONTINUE; - } - - public STATE onContentWriteCompleted() { - operationCompleted.set(true); - return STATE.CONTINUE; - } - - @Override - public Response onCompleted(Response response) throws Exception { - return response; - } - }).get(); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); - assertTrue(operationCompleted.get()); - assertTrue(headerSent.get()); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void zeroCopyPutTest() throws IOException, ExecutionException, TimeoutException, InterruptedException, URISyntaxException { - AsyncHttpClient client = getAsyncHttpClient(null); - try { - Future f = client.preparePut("http://127.0.0.1/" + port1 + "/").setBody(SIMPLE_TEXT_FILE).execute(); - Response resp = f.get(); - assertNotNull(resp); - assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); - assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); - } finally { - client.close(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new ZeroCopyHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void zeroCopyFileTest() throws IOException, ExecutionException, TimeoutException, InterruptedException, URISyntaxException { - AsyncHttpClient client = getAsyncHttpClient(null); - File tmp = new File(System.getProperty("java.io.tmpdir") + File.separator + "zeroCopy.txt"); - tmp.deleteOnExit(); - final FileOutputStream stream = new FileOutputStream(tmp); - try { - Response resp = client.preparePost("http://127.0.0.1/" + port1 + "/").setBody(SIMPLE_TEXT_FILE).execute(new AsyncHandler() { - public void onThrowable(Throwable t) { - } - - public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - bodyPart.writeTo(stream); - return STATE.CONTINUE; - } - - public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { - return STATE.CONTINUE; - } - - public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { - return STATE.CONTINUE; - } - - public Response onCompleted() throws Exception { - return null; - } - }).get(); - assertNull(resp); - assertEquals(SIMPLE_TEXT_FILE.length(), tmp.length()); - } finally { - stream.close(); - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void zeroCopyFileWithBodyManipulationTest() throws IOException, ExecutionException, TimeoutException, InterruptedException, URISyntaxException { - AsyncHttpClient client = getAsyncHttpClient(null); - File tmp = new File(System.getProperty("java.io.tmpdir") + File.separator + "zeroCopy.txt"); - tmp.deleteOnExit(); - final FileOutputStream stream = new FileOutputStream(tmp); - try { - - Response resp = client.preparePost("http://127.0.0.1/" + port1 + "/").setBody(SIMPLE_TEXT_FILE).execute(new AsyncHandler() { - public void onThrowable(Throwable t) { - } - - public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - bodyPart.writeTo(stream); - - if (bodyPart.getBodyPartBytes().length == 0) { - return STATE.ABORT; - } - - return STATE.CONTINUE; - } - - public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { - return STATE.CONTINUE; - } - - public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { - return STATE.CONTINUE; - } - - public Response onCompleted() throws Exception { - return null; - } - }).get(); - assertNull(resp); - assertEquals(SIMPLE_TEXT_FILE.length(), tmp.length()); - } finally { - stream.close(); - client.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/async/util/EchoHandler.java b/api/src/test/java/org/asynchttpclient/async/util/EchoHandler.java deleted file mode 100644 index 00e132c849..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/util/EchoHandler.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.asynchttpclient.async.util; - -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; - -import javax.servlet.ServletException; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.Enumeration; - -public class EchoHandler extends AbstractHandler { - - @Override - public void handle(String pathInContext, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { - - if (httpRequest.getHeader("X-HEAD") != null) { - httpResponse.setContentLength(1); - } - - if (httpRequest.getHeader("X-ISO") != null) { - httpResponse.setContentType(TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_ISO_8859_1_CHARSET); - } else { - httpResponse.setContentType(TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); - } - - if (request.getMethod().equalsIgnoreCase("OPTIONS")) { - httpResponse.addHeader("Allow", "GET,HEAD,POST,OPTIONS,TRACE"); - } - ; - - Enumeration e = httpRequest.getHeaderNames(); - String param; - while (e.hasMoreElements()) { - param = e.nextElement().toString(); - - if (param.startsWith("LockThread")) { - try { - Thread.sleep(40 * 1000); - } catch (InterruptedException ex) { - } - } - - if (param.startsWith("X-redirect")) { - httpResponse.sendRedirect(httpRequest.getHeader("X-redirect")); - return; - } - httpResponse.addHeader("X-" + param, httpRequest.getHeader(param)); - } - - Enumeration i = httpRequest.getParameterNames(); - - StringBuilder requestBody = new StringBuilder(); - while (i.hasMoreElements()) { - param = i.nextElement().toString(); - httpResponse.addHeader("X-" + param, httpRequest.getParameter(param)); - requestBody.append(param); - requestBody.append("_"); - } - - String pathInfo = httpRequest.getPathInfo(); - if (pathInfo != null) - httpResponse.addHeader("X-pathInfo", pathInfo); - - String queryString = httpRequest.getQueryString(); - if (queryString != null) - httpResponse.addHeader("X-queryString", queryString); - - httpResponse.addHeader("X-KEEP-ALIVE", httpRequest.getRemoteAddr() + ":" + httpRequest.getRemotePort()); - - Cookie[] cs = httpRequest.getCookies(); - if (cs != null) { - for (Cookie c : cs) { - httpResponse.addCookie(c); - } - } - - if (requestBody.length() > 0) { - httpResponse.getOutputStream().write(requestBody.toString().getBytes()); - } - - int size = 16384; - if (httpRequest.getContentLength() > 0) { - size = httpRequest.getContentLength(); - } - byte[] bytes = new byte[size]; - if (bytes.length > 0) { - int read = 0; - while (read > -1) { - read = httpRequest.getInputStream().read(bytes); - if (read > 0) { - httpResponse.getOutputStream().write(bytes, 0, read); - } - } - } - - httpResponse.setStatus(200); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - } -} \ No newline at end of file diff --git a/api/src/test/java/org/asynchttpclient/async/util/TestUtils.java b/api/src/test/java/org/asynchttpclient/async/util/TestUtils.java deleted file mode 100644 index ad87080b85..0000000000 --- a/api/src/test/java/org/asynchttpclient/async/util/TestUtils.java +++ /dev/null @@ -1,283 +0,0 @@ -package org.asynchttpclient.async.util; - -import static org.testng.Assert.assertEquals; - -import org.apache.commons.io.FileUtils; -import org.asynchttpclient.util.StandardCharsets; -import org.eclipse.jetty.security.ConstraintMapping; -import org.eclipse.jetty.security.ConstraintSecurityHandler; -import org.eclipse.jetty.security.HashLoginService; -import org.eclipse.jetty.security.LoginService; -import org.eclipse.jetty.security.authentication.BasicAuthenticator; -import org.eclipse.jetty.security.authentication.DigestAuthenticator; -import org.eclipse.jetty.security.authentication.LoginAuthenticator; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.HttpConnectionFactory; -import org.eclipse.jetty.server.SecureRequestCustomizer; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.SslConnectionFactory; -import org.eclipse.jetty.util.security.Constraint; -import org.eclipse.jetty.util.ssl.SslContextFactory; - -import javax.net.ssl.*; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.ServerSocket; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.Charset; -import java.security.*; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; - -public class TestUtils { - - public static final String USER = "user"; - public static final String ADMIN = "admin"; - public static final String TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET = "text/html; charset=UTF-8"; - public static final String TEXT_HTML_CONTENT_TYPE_WITH_ISO_8859_1_CHARSET = "text/html; charset=ISO-8859-1"; - private static final File TMP_DIR = new File(System.getProperty("java.io.tmpdir"), "ahc-tests-" + UUID.randomUUID().toString().substring(0, 8)); - public static final byte[] PATTERN_BYTES = "FooBarBazQixFooBarBazQixFooBarBazQixFooBarBazQixFooBarBazQixFooBarBazQix".getBytes(Charset.forName("UTF-16")); - public static final File LARGE_IMAGE_FILE; - public static byte[] LARGE_IMAGE_BYTES; - public static final File SIMPLE_TEXT_FILE; - public static final String SIMPLE_TEXT_FILE_STRING; - private static final LoginService LOGIN_SERVICE = new HashLoginService("MyRealm", "src/test/resources/realm.properties"); - - static { - try { - TMP_DIR.mkdirs(); - TMP_DIR.deleteOnExit(); - LARGE_IMAGE_FILE = new File(TestUtils.class.getClassLoader().getResource("300k.png").toURI()); - LARGE_IMAGE_BYTES = FileUtils.readFileToByteArray(LARGE_IMAGE_FILE); - SIMPLE_TEXT_FILE = new File(TestUtils.class.getClassLoader().getResource("SimpleTextFile.txt").toURI()); - SIMPLE_TEXT_FILE_STRING = FileUtils.readFileToString(SIMPLE_TEXT_FILE, StandardCharsets.UTF_8); - } catch (Exception e) { - throw new ExceptionInInitializerError(e); - } - } - - public static synchronized int findFreePort() throws IOException { - ServerSocket socket = null; - - try { - socket = new ServerSocket(0); - - return socket.getLocalPort(); - } finally { - if (socket != null) - socket.close(); - } - } - - public static File createTempFile(int approxSize) throws IOException { - long repeats = approxSize / TestUtils.PATTERN_BYTES.length + 1; - File tmpFile = File.createTempFile("tmpfile-", ".data", TMP_DIR); - tmpFile.deleteOnExit(); - FileOutputStream out = null; - try { - out = new FileOutputStream(tmpFile); - for (int i = 0; i < repeats; i++) { - out.write(PATTERN_BYTES); - } - - long expectedFileSize = PATTERN_BYTES.length * repeats; - assertEquals(tmpFile.length(), expectedFileSize, "Invalid file length"); - - return tmpFile; - } finally { - if (out != null) { - out.close(); - } - } - } - - public static Server newJettyHttpServer(int port) { - Server server = new Server(); - addHttpConnector(server, port); - return server; - } - - public static void addHttpConnector(Server server, int port) { - ServerConnector connector = new ServerConnector(server); - connector.setPort(port); - - server.addConnector(connector); - } - - public static Server newJettyHttpsServer(int port) throws URISyntaxException { - Server server = new Server(); - addHttpsConnector(server, port); - return server; - } - - public static void addHttpsConnector(Server server, int port) throws URISyntaxException { - ClassLoader cl = TestUtils.class.getClassLoader(); - - URL keystoreUrl = cl.getResource("ssltest-keystore.jks"); - String keyStoreFile = new File(keystoreUrl.toURI()).getAbsolutePath(); - SslContextFactory sslContextFactory = new SslContextFactory(keyStoreFile); - sslContextFactory.setKeyStorePassword("changeit"); - - String trustStoreFile = new File(cl.getResource("ssltest-cacerts.jks").toURI()).getAbsolutePath(); - sslContextFactory.setTrustStorePath(trustStoreFile); - sslContextFactory.setTrustStorePassword("changeit"); - - HttpConfiguration httpsConfig = new HttpConfiguration(); - httpsConfig.setSecureScheme("https"); - httpsConfig.setSecurePort(port); - httpsConfig.addCustomizer(new SecureRequestCustomizer()); - - ServerConnector connector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"), new HttpConnectionFactory(httpsConfig)); - connector.setPort(port); - - server.addConnector(connector); - } - - public static void addBasicAuthHandler(Server server, boolean strict, Handler handler) { - addAuthHandler(server, Constraint.__BASIC_AUTH, new BasicAuthenticator(), strict, handler); - } - - public static void addDigestAuthHandler(Server server, boolean strict, Handler handler) { - addAuthHandler(server, Constraint.__DIGEST_AUTH, new DigestAuthenticator(), strict, handler); - } - - private static void addAuthHandler(Server server, String auth, LoginAuthenticator authenticator, boolean strict, Handler handler) { - - server.addBean(LOGIN_SERVICE); - - Constraint constraint = new Constraint(); - constraint.setName(auth); - constraint.setRoles(new String[] { USER, ADMIN }); - constraint.setAuthenticate(true); - - ConstraintMapping mapping = new ConstraintMapping(); - mapping.setConstraint(constraint); - mapping.setPathSpec("/*"); - - Set knownRoles = new HashSet(); - knownRoles.add(USER); - knownRoles.add(ADMIN); - - List cm = new ArrayList(); - cm.add(mapping); - - ConstraintSecurityHandler security = new ConstraintSecurityHandler(); - security.setConstraintMappings(cm, knownRoles); - security.setAuthenticator(authenticator); - security.setLoginService(LOGIN_SERVICE); - security.setStrict(strict); - security.setHandler(handler); - server.setHandler(security); - } - - private static KeyManager[] createKeyManagers() throws GeneralSecurityException, IOException { - InputStream keyStoreStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("ssltest-cacerts.jks"); - char[] keyStorePassword = "changeit".toCharArray(); - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(keyStoreStream, keyStorePassword); - assert(ks.size() > 0); - - // Set up key manager factory to use our key store - char[] certificatePassword = "changeit".toCharArray(); - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); - kmf.init(ks, certificatePassword); - - // Initialize the SSLContext to work with our key managers. - return kmf.getKeyManagers(); - } - - private static TrustManager[] createTrustManagers() throws GeneralSecurityException, IOException { - InputStream keyStoreStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("ssltest-keystore.jks"); - char[] keyStorePassword = "changeit".toCharArray(); - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(keyStoreStream, keyStorePassword); - assert(ks.size() > 0); - - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init(ks); - return tmf.getTrustManagers(); - } - - public static SSLContext createSSLContext(AtomicBoolean trust) { - try { - KeyManager[] keyManagers = createKeyManagers(); - TrustManager[] trustManagers = new TrustManager[] { dummyTrustManager(trust, (X509TrustManager) createTrustManagers()[0]) }; - SecureRandom secureRandom = new SecureRandom(); - - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(keyManagers, trustManagers, secureRandom); - - return sslContext; - } catch (Exception e) { - throw new Error("Failed to initialize the server-side SSLContext", e); - } - } - - public static class DummyTrustManager implements X509TrustManager { - - private final X509TrustManager tm; - private final AtomicBoolean trust; - - public DummyTrustManager(final AtomicBoolean trust, final X509TrustManager tm) { - this.trust = trust; - this.tm = tm; - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) - throws CertificateException { - tm.checkClientTrusted(chain, authType); - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) - throws CertificateException { - if (!trust.get()) { - throw new CertificateException("Server certificate not trusted."); - } - tm.checkServerTrusted(chain, authType); - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return tm.getAcceptedIssuers(); - } - } - - private static TrustManager dummyTrustManager(final AtomicBoolean trust, final X509TrustManager tm) { - return new DummyTrustManager(trust, tm); - - } - - public static File getClasspathFile(String file) throws FileNotFoundException { - ClassLoader cl = null; - try { - cl = Thread.currentThread().getContextClassLoader(); - } catch (Throwable ex) { - } - if (cl == null) { - cl = TestUtils.class.getClassLoader(); - } - URL resourceUrl = cl.getResource(file); - - try { - return new File(new URI(resourceUrl.toString()).getSchemeSpecificPart()); - } catch (URISyntaxException e) { - throw new FileNotFoundException(file); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/cookie/CookieDecoderTest.java b/api/src/test/java/org/asynchttpclient/cookie/CookieDecoderTest.java deleted file mode 100644 index f14aaf0b78..0000000000 --- a/api/src/test/java/org/asynchttpclient/cookie/CookieDecoderTest.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.cookie; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.testng.annotations.Test; - -public class CookieDecoderTest { - - @Test(groups = "fast") - public void testDecodeUnquoted() { - Cookie cookie = CookieDecoder.decode("foo=value; domain=/; path=/"); - assertNotNull(cookie); - assertEquals(cookie.getValue(), "value"); - assertEquals(cookie.getRawValue(), "value"); - assertEquals(cookie.getDomain(), "/"); - assertEquals(cookie.getPath(), "/"); - } - - @Test(groups = "fast") - public void testDecodeQuoted() { - Cookie cookie = CookieDecoder.decode("ALPHA=\"VALUE1\"; Domain=docs.foo.com; Path=/accounts; Expires=Wed, 05 Feb 2014 07:37:38 GMT; Secure; HttpOnly"); - assertNotNull(cookie); - assertEquals(cookie.getValue(), "VALUE1"); - assertEquals(cookie.getRawValue(), "\"VALUE1\""); - } - - @Test(groups = "fast") - public void testDecodeQuotedContainingEscapedQuote() { - Cookie cookie = CookieDecoder.decode("ALPHA=\"VALUE1\\\"\"; Domain=docs.foo.com; Path=/accounts; Expires=Wed, 05 Feb 2014 07:37:38 GMT; Secure; HttpOnly"); - assertNotNull(cookie); - assertEquals(cookie.getValue(), "VALUE1\""); - assertEquals(cookie.getRawValue(), "\"VALUE1\\\"\""); - } -} diff --git a/api/src/test/java/org/asynchttpclient/date/RFC2616DateParserTest.java b/api/src/test/java/org/asynchttpclient/date/RFC2616DateParserTest.java deleted file mode 100644 index ed171f6922..0000000000 --- a/api/src/test/java/org/asynchttpclient/date/RFC2616DateParserTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.date; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.testng.annotations.Test; - -/** - * See http://tools.ietf.org/html/rfc2616#section-3.3 - * - * @author slandelle - */ -public class RFC2616DateParserTest { - - @Test(groups = "fast") - public void testRFC822() { - RFC2616Date date = new RFC2616DateParser("Sun, 06 Nov 1994 08:49:37 GMT").parse(); - assertNotNull(date); - assertEquals(date.dayOfMonth(), 6); - assertEquals(date.month(), 11); - assertEquals(date.year(), 1994); - assertEquals(date.hour(), 8); - assertEquals(date.minute(), 49); - assertEquals(date.second(), 37); - } - - @Test(groups = "fast") - public void testRFC822SingleDigitDayOfMonth() { - RFC2616Date date = new RFC2616DateParser("Sun, 6 Nov 1994 08:49:37 GMT").parse(); - assertNotNull(date); - assertEquals(date.dayOfMonth(), 6); - } - - @Test(groups = "fast") - public void testRFC822TwoDigitsYear() { - RFC2616Date date = new RFC2616DateParser("Sun, 6 Nov 94 08:49:37 GMT").parse(); - assertNotNull(date); - assertEquals(date.year(), 1994); - } - - @Test(groups = "fast") - public void testRFC822SingleDigitHour() { - RFC2616Date date = new RFC2616DateParser("Sun, 6 Nov 1994 8:49:37 GMT").parse(); - assertNotNull(date); - assertEquals(date.hour(), 8); - } - - @Test(groups = "fast") - public void testRFC822SingleDigitMinute() { - RFC2616Date date = new RFC2616DateParser("Sun, 6 Nov 1994 08:9:37 GMT").parse(); - assertNotNull(date); - assertEquals(date.minute(), 9); - } - - @Test(groups = "fast") - public void testRFC822SingleDigitSecond() { - RFC2616Date date = new RFC2616DateParser("Sun, 6 Nov 1994 08:49:7 GMT").parse(); - assertNotNull(date); - assertEquals(date.second(), 7); - } - - @Test(groups = "fast") - public void testRFC6265() { - RFC2616Date date = new RFC2616DateParser("Sun, 06 Nov 1994 08:49:37").parse(); - assertNotNull(date); - assertEquals(date.dayOfMonth(), 6); - assertEquals(date.month(), 11); - assertEquals(date.year(), 1994); - assertEquals(date.hour(), 8); - assertEquals(date.minute(), 49); - assertEquals(date.second(), 37); - } - - @Test(groups = "fast") - public void testRFC850() { - RFC2616Date date = new RFC2616DateParser("Sunday, 06-Nov-94 08:49:37 GMT").parse(); - assertNotNull(date); - assertEquals(date.dayOfMonth(), 6); - assertEquals(date.month(), 11); - assertEquals(date.year(), 1994); - assertEquals(date.hour(), 8); - assertEquals(date.minute(), 49); - assertEquals(date.second(), 37); - } - - @Test(groups = "fast") - public void testANSIC() { - RFC2616Date date = new RFC2616DateParser("Sun Nov 6 08:49:37 1994").parse(); - assertNotNull(date); - assertEquals(date.dayOfMonth(), 6); - assertEquals(date.month(), 11); - assertEquals(date.year(), 1994); - assertEquals(date.hour(), 8); - assertEquals(date.minute(), 49); - assertEquals(date.second(), 37); - } -} diff --git a/api/src/test/java/org/asynchttpclient/generators/ByteArrayBodyGeneratorTest.java b/api/src/test/java/org/asynchttpclient/generators/ByteArrayBodyGeneratorTest.java deleted file mode 100644 index 80645ba1eb..0000000000 --- a/api/src/test/java/org/asynchttpclient/generators/ByteArrayBodyGeneratorTest.java +++ /dev/null @@ -1,75 +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.generators; - -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.Body; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Random; - -/** - * @author Bryan Davis bpd@keynetics.com - */ -public class ByteArrayBodyGeneratorTest { - - private final Random random = new Random(); - private final int chunkSize = 1024 * 8; - - @Test(groups = "standalone") - public void testSingleRead() throws IOException { - final int srcArraySize = chunkSize - 1; - final byte[] srcArray = new byte[srcArraySize]; - random.nextBytes(srcArray); - - final ByteArrayBodyGenerator babGen = - new ByteArrayBodyGenerator(srcArray); - final Body body = babGen.createBody(); - - final ByteBuffer chunkBuffer = ByteBuffer.allocate(chunkSize); - - // should take 1 read to get through the srcArray - assertEquals(body.read(chunkBuffer), srcArraySize); - assertEquals(chunkBuffer.position(), srcArraySize, "bytes read"); - chunkBuffer.clear(); - - assertEquals(body.read(chunkBuffer), -1, "body at EOF"); - } - - @Test(groups = "standalone") - public void testMultipleReads() throws IOException { - final int srcArraySize = (3 * chunkSize) + 42; - final byte[] srcArray = new byte[srcArraySize]; - random.nextBytes(srcArray); - - final ByteArrayBodyGenerator babGen = - new ByteArrayBodyGenerator(srcArray); - final Body body = babGen.createBody(); - - final ByteBuffer chunkBuffer = ByteBuffer.allocate(chunkSize); - - int reads = 0; - int bytesRead = 0; - while (body.read(chunkBuffer) != -1) { - reads += 1; - bytesRead += chunkBuffer.position(); - chunkBuffer.clear(); - } - assertEquals(reads, 4, "reads to drain generator"); - assertEquals(bytesRead, srcArraySize, "bytes read"); - } -} diff --git a/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java b/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java deleted file mode 100644 index 3eeb05bbe6..0000000000 --- a/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2013 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.multipart; - -import static org.asynchttpclient.util.StandardCharsets.UTF_8; - -import org.asynchttpclient.Body; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -public class MultipartBodyTest { - - @Test(groups = "fast") - public void testBasics() { - final List parts = new ArrayList(); - - // add a file - final File testFile = getTestfile(); - parts.add(new FilePart("filePart", testFile)); - - // add a byte array - parts.add(new ByteArrayPart("baPart", "testMultiPart".getBytes(UTF_8), "application/test", UTF_8, "fileName")); - - // add a string - parts.add(new StringPart("stringPart", "testString", UTF_8)); - - compareContentLength(parts); - } - - private static File getTestfile() { - final ClassLoader cl = MultipartBodyTest.class.getClassLoader(); - final URL url = cl.getResource("textfile.txt"); - Assert.assertNotNull(url); - File file = null; - try { - file = new File(url.toURI()); - } catch (URISyntaxException use) { - Assert.fail("uri syntax error"); - } - return file; - } - - private static void compareContentLength(final List parts) { - Assert.assertNotNull(parts); - // get expected values - final Body multipartBody = MultipartUtils.newMultipartBody(parts, new FluentCaseInsensitiveStringsMap()); - final long expectedContentLength = multipartBody.getContentLength(); - try { - final ByteBuffer buffer = ByteBuffer.allocate(8192); - boolean last = false; - long totalBytes = 0; - while (!last) { - long readBytes = 0; - try { - readBytes = multipartBody.read(buffer); - } catch (IOException ie) { - Assert.fail("read failure"); - } - if (readBytes > 0) { - totalBytes += readBytes; - } else { - last = true; - } - buffer.clear(); - } - Assert.assertEquals(totalBytes, expectedContentLength); - } finally { - try { - multipartBody.close(); - } catch (IOException ignore) { - } - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/oauth/TestSignatureCalculator.java b/api/src/test/java/org/asynchttpclient/oauth/TestSignatureCalculator.java deleted file mode 100644 index 268cd58e98..0000000000 --- a/api/src/test/java/org/asynchttpclient/oauth/TestSignatureCalculator.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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 static org.testng.Assert.assertEquals; - -import java.util.ArrayList; -import java.util.List; - -import org.asynchttpclient.Param; -import org.asynchttpclient.uri.UriComponents; -import org.testng.annotations.Test; - -public class TestSignatureCalculator { - private static final String CONSUMER_KEY = "dpf43f3p2l4k3l03"; - - private static final String CONSUMER_SECRET = "kd94hf93k423kf44"; - - public static final String TOKEN_KEY = "nnch734d00sl2jdk"; - - public static final String TOKEN_SECRET = "pfkkdhi9sl3r4s00"; - - public static final String NONCE = "kllo9940pd9333jh"; - - final static long TIMESTAMP = 1191242096; - - // based on the reference test case from - // http://oauth.pbwiki.com/TestCases - @Test(groups = "fast") - public void test() { - ConsumerKey consumer = new ConsumerKey(CONSUMER_KEY, CONSUMER_SECRET); - RequestToken user = new RequestToken(TOKEN_KEY, TOKEN_SECRET); - OAuthSignatureCalculator calc = new OAuthSignatureCalculator(consumer, user); - List queryParams = new ArrayList(); - queryParams.add(new Param("file", "vacation.jpg")); - queryParams.add(new Param("size", "original")); - String url = "http://photos.example.net/photos"; - String sig = calc.calculateSignature("GET", UriComponents.create(url), TIMESTAMP, NONCE, null, queryParams); - - assertEquals(sig, "tR3+Ty81lMeYAr/Fid0kMTYa/WM="); - } -} diff --git a/api/src/test/java/org/asynchttpclient/resumable/MapResumableProcessor.java b/api/src/test/java/org/asynchttpclient/resumable/MapResumableProcessor.java deleted file mode 100644 index 3dbf4e8276..0000000000 --- a/api/src/test/java/org/asynchttpclient/resumable/MapResumableProcessor.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.asynchttpclient.resumable; - -import org.asynchttpclient.resumable.ResumableAsyncHandler.ResumableProcessor; - -import java.util.HashMap; -import java.util.Map; - -/** - * @author Benjamin Hanzelmann - */ -public class MapResumableProcessor - implements ResumableProcessor { - - Map map = new HashMap(); - - public void put(String key, long transferredBytes) { - map.put(key, transferredBytes); - } - - public void remove(String key) { - map.remove(key); - } - - /** - * NOOP - */ - public void save(Map map) { - - } - - /** - * NOOP - */ - public Map load() { - return map; - } -} \ No newline at end of file diff --git a/api/src/test/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcesserTest.java b/api/src/test/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcesserTest.java deleted file mode 100644 index b5de1de8cf..0000000000 --- a/api/src/test/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcesserTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.asynchttpclient.resumable; - -/* - * Copyright (c) 2010 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. - */ - -import static org.testng.Assert.assertEquals; - -import org.testng.annotations.Test; - -import java.util.Map; - -/** - * @author Benjamin Hanzelmann - */ -public class PropertiesBasedResumableProcesserTest { - @Test - public void testSaveLoad() throws Exception { - PropertiesBasedResumableProcessor p = new PropertiesBasedResumableProcessor(); - p.put("http://localhost/test.url", 15L); - p.put("http://localhost/test2.url", 50L); - p.save(null); - p = new PropertiesBasedResumableProcessor(); - Map m = p.load(); - assertEquals(m.size(), 2); - assertEquals(m.get("http://localhost/test.url"), Long.valueOf(15L)); - assertEquals(m.get("http://localhost/test2.url"), Long.valueOf(50L)); - } -} diff --git a/api/src/test/java/org/asynchttpclient/resumable/ResumableAsyncHandlerTest.java b/api/src/test/java/org/asynchttpclient/resumable/ResumableAsyncHandlerTest.java deleted file mode 100644 index 196b2e4df6..0000000000 --- a/api/src/test/java/org/asynchttpclient/resumable/ResumableAsyncHandlerTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.asynchttpclient.resumable; - -/* - * Copyright (c) 2010 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. - */ - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; - -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.testng.annotations.Test; - -/** - * @author Benjamin Hanzelmann - */ -public class ResumableAsyncHandlerTest { - @Test - public void testAdjustRange() { - MapResumableProcessor proc = new MapResumableProcessor(); - - ResumableAsyncHandler h = new ResumableAsyncHandler(proc); - Request request = new RequestBuilder("GET").setUrl("http://test/url").build(); - Request newRequest = h.adjustRequestRange(request); - assertEquals(newRequest.getURI(), request.getURI()); - String rangeHeader = newRequest.getHeaders().getFirstValue("Range"); - assertNull(rangeHeader); - - proc.put("http://test/url", 5000); - newRequest = h.adjustRequestRange(request); - assertEquals(newRequest.getURI(), request.getURI()); - rangeHeader = newRequest.getHeaders().getFirstValue("Range"); - assertEquals(rangeHeader, "bytes=5000-"); - } -} diff --git a/api/src/test/java/org/asynchttpclient/util/ProxyUtilsTest.java b/api/src/test/java/org/asynchttpclient/util/ProxyUtilsTest.java deleted file mode 100644 index fdcb8884bd..0000000000 --- a/api/src/test/java/org/asynchttpclient/util/ProxyUtilsTest.java +++ /dev/null @@ -1,48 +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.util; - -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.testng.annotations.Test; - -public class ProxyUtilsTest { - @Test(groups = "fast") - public void testBasics() { - // should avoid, there is no proxy (is null) - Request req = new RequestBuilder("GET").setUrl("http://somewhere.com/foo").build(); - assertTrue(ProxyUtils.avoidProxy(null, req)); - - // should avoid, it's in non-proxy hosts - req = new RequestBuilder("GET").setUrl("http://somewhere.com/foo").build(); - ProxyServer proxyServer = new ProxyServer("foo", 1234); - proxyServer.addNonProxyHost("somewhere.com"); - assertTrue(ProxyUtils.avoidProxy(proxyServer, req)); - - // should avoid, it's in non-proxy hosts (with "*") - req = new RequestBuilder("GET").setUrl("http://sub.somewhere.com/foo").build(); - proxyServer = new ProxyServer("foo", 1234); - proxyServer.addNonProxyHost("*.somewhere.com"); - assertTrue(ProxyUtils.avoidProxy(proxyServer, req)); - - // should use it - req = new RequestBuilder("GET").setUrl("http://sub.somewhere.com/foo").build(); - proxyServer = new ProxyServer("foo", 1234); - proxyServer.addNonProxyHost("*.somewhere.org"); - assertFalse(ProxyUtils.avoidProxy(proxyServer, req)); - } -} diff --git a/api/src/test/java/org/asynchttpclient/util/TestUTF8UrlCodec.java b/api/src/test/java/org/asynchttpclient/util/TestUTF8UrlCodec.java deleted file mode 100644 index 3b1dcdfba3..0000000000 --- a/api/src/test/java/org/asynchttpclient/util/TestUTF8UrlCodec.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.util; - -import static org.testng.Assert.assertEquals; - -import org.testng.annotations.Test; - -public class TestUTF8UrlCodec { - @Test(groups = "fast") - public void testBasics() { - assertEquals(UTF8UrlEncoder.encode("foobar"), "foobar"); - assertEquals(UTF8UrlEncoder.encode("a&b"), "a%26b"); - assertEquals(UTF8UrlEncoder.encode("a+b"), "a%2Bb"); - } -} diff --git a/api/src/test/java/org/asynchttpclient/websocket/AbstractBasicTest.java b/api/src/test/java/org/asynchttpclient/websocket/AbstractBasicTest.java deleted file mode 100644 index f5f944d0c1..0000000000 --- a/api/src/test/java/org/asynchttpclient/websocket/AbstractBasicTest.java +++ /dev/null @@ -1,45 +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.websocket; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; - -import org.eclipse.jetty.websocket.server.WebSocketHandler; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; - -public abstract class AbstractBasicTest extends org.asynchttpclient.async.AbstractBasicTest { - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - - port1 = findFreePort(); - server = newJettyHttpServer(port1); - server.setHandler(getWebSocketHandler()); - - server.start(); - logger.info("Local HTTP server started successfully"); - } - - @AfterClass(alwaysRun = true) - public void tearDownGlobal() throws Exception { - server.stop(); - } - - protected String getTargetUrl() { - return String.format("ws://127.0.0.1:%d/", port1); - } - - public abstract WebSocketHandler getWebSocketHandler(); -} diff --git a/api/src/test/java/org/asynchttpclient/websocket/ByteMessageTest.java b/api/src/test/java/org/asynchttpclient/websocket/ByteMessageTest.java deleted file mode 100644 index d1078047ec..0000000000 --- a/api/src/test/java/org/asynchttpclient/websocket/ByteMessageTest.java +++ /dev/null @@ -1,218 +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.websocket; - -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -import org.eclipse.jetty.websocket.server.WebSocketHandler; -import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.testng.annotations.Test; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; - -public abstract class ByteMessageTest extends AbstractBasicTest { - - @Override - public WebSocketHandler getWebSocketHandler() { - return new WebSocketHandler() { - @Override - public void configure(WebSocketServletFactory factory) { - factory.register(EchoSocket.class); - } - }; - } - - @Test - public void echoByte() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference text = new AtomicReference(new byte[0]); - - WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketByteListener() { - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - - @Override - public void onMessage(byte[] message) { - text.set(message); - latch.countDown(); - } - - }).build()).get(); - - websocket.sendMessage("ECHO".getBytes()); - - latch.await(); - assertEquals(text.get(), "ECHO".getBytes()); - } finally { - c.close(); - } - } - - @Test - public void echoTwoMessagesTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(2); - final AtomicReference text = new AtomicReference(null); - - WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketByteListener() { - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - - @Override - public void onMessage(byte[] message) { - if (text.get() == null) { - text.set(message); - } else { - byte[] n = new byte[text.get().length + message.length]; - System.arraycopy(text.get(), 0, n, 0, text.get().length); - System.arraycopy(message, 0, n, text.get().length, message.length); - text.set(n); - } - latch.countDown(); - } - - }).build()).get(); - - websocket.sendMessage("ECHO".getBytes()).sendMessage("ECHO".getBytes()); - - latch.await(); - assertEquals(text.get(), "ECHOECHO".getBytes()); - } finally { - c.close(); - } - } - - @Test - public void echoOnOpenMessagesTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(2); - final AtomicReference text = new AtomicReference(null); - - /* WebSocket websocket = */c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketByteListener() { - - @Override - public void onOpen(WebSocket websocket) { - websocket.sendMessage("ECHO".getBytes()).sendMessage("ECHO".getBytes()); - } - - @Override - public void onClose(WebSocket websocket) { - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - - @Override - public void onMessage(byte[] message) { - if (text.get() == null) { - text.set(message); - } else { - byte[] n = new byte[text.get().length + message.length]; - System.arraycopy(text.get(), 0, n, 0, text.get().length); - System.arraycopy(message, 0, n, text.get().length, message.length); - text.set(n); - } - latch.countDown(); - } - - }).build()).get(); - - latch.await(); - assertEquals(text.get(), "ECHOECHO".getBytes()); - } finally { - c.close(); - } - } - - public void echoFragments() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference text = new AtomicReference(null); - - WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketByteListener() { - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - - @Override - public void onMessage(byte[] message) { - if (text.get() == null) { - text.set(message); - } else { - byte[] n = new byte[text.get().length + message.length]; - System.arraycopy(text.get(), 0, n, 0, text.get().length); - System.arraycopy(message, 0, n, text.get().length, message.length); - text.set(n); - } - latch.countDown(); - } - - }).build()).get(); - websocket.stream("ECHO".getBytes(), false); - websocket.stream("ECHO".getBytes(), true); - latch.await(); - assertEquals(text.get(), "ECHOECHO".getBytes()); - } finally { - c.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/websocket/CloseCodeReasonMessageTest.java b/api/src/test/java/org/asynchttpclient/websocket/CloseCodeReasonMessageTest.java deleted file mode 100644 index 5ab3ca228d..0000000000 --- a/api/src/test/java/org/asynchttpclient/websocket/CloseCodeReasonMessageTest.java +++ /dev/null @@ -1,176 +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.websocket; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.AsyncHttpClient; -import org.eclipse.jetty.websocket.server.WebSocketHandler; -import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.testng.annotations.Test; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; - -public abstract class CloseCodeReasonMessageTest extends AbstractBasicTest { - - @Override - public WebSocketHandler getWebSocketHandler() { - return new WebSocketHandler() { - @Override - public void configure(WebSocketServletFactory factory) { - factory.register(EchoSocket.class); - } - }; - } - - @Test(timeOut = 60000) - public void onCloseWithCode() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference text = new AtomicReference(""); - - WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new Listener(latch, text)).build()).get(); - - websocket.close(); - - latch.await(); - assertTrue(text.get().startsWith("1000")); - } finally { - c.close(); - } - } - - @Test(timeOut = 60000) - public void onCloseWithCodeServerClose() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference text = new AtomicReference(""); - - c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new Listener(latch, text)).build()).get(); - - latch.await(); - assertEquals(text.get(), "1001-Idle Timeout"); - } finally { - c.close(); - } - } - - public final static class Listener implements WebSocketListener, - WebSocketCloseCodeReasonListener { - - final CountDownLatch latch; - final AtomicReference text; - - public Listener(CountDownLatch latch, AtomicReference text) { - this.latch = latch; - this.text = text; - } - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - latch.countDown(); - } - - public void onClose(WebSocket websocket, int code, String reason) { - text.set(code + "-" + reason); - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - } - - @Test(timeOut = 60000) - public void wrongStatusCode() throws Throwable { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference throwable = new AtomicReference(); - - c.prepareGet("http://apache.org/").execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { - - @Override - public void onMessage(String message) { - } - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - } - - @Override - public void onError(Throwable t) { - throwable.set(t); - latch.countDown(); - } - }).build()).get(); - - latch.await(); - assertNotNull(throwable.get()); - assertEquals(throwable.get().getClass(), IllegalStateException.class); - } finally { - c.close(); - } - } - - @Test(timeOut = 60000) - public void wrongProtocolCode() throws Throwable { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference throwable = new AtomicReference(); - - c.prepareGet("ws://www.google.com").execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { - - @Override - public void onMessage(String message) { - } - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - } - - @Override - public void onError(Throwable t) { - throwable.set(t); - latch.countDown(); - } - }).build()).get(); - - latch.await(); - assertNotNull(throwable.get()); - assertEquals(throwable.get().getClass(), IllegalStateException.class); - } finally { - c.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/websocket/EchoSocket.java b/api/src/test/java/org/asynchttpclient/websocket/EchoSocket.java deleted file mode 100644 index 9bc51fc790..0000000000 --- a/api/src/test/java/org/asynchttpclient/websocket/EchoSocket.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.asynchttpclient.websocket; - -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketAdapter; - -import java.io.IOException; -import java.nio.ByteBuffer; - -public class EchoSocket extends WebSocketAdapter { - - @Override - public void onWebSocketConnect(Session sess) { - super.onWebSocketConnect(sess); - sess.setIdleTimeout(10000); - sess.setMaximumMessageSize(1000); - } - - @Override - public void onWebSocketClose(int statusCode, String reason) { - try { - getSession().close(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - super.onWebSocketClose(statusCode, reason); - } - - @Override - public void onWebSocketBinary(byte[] payload, int offset, int len) { - if (isNotConnected()) { - return; - } - try { - getRemote().sendBytes(ByteBuffer.wrap(payload, offset, len)); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public void onWebSocketText(String message) { - if (isNotConnected()) { - return; - } - try { - if (message.equals("CLOSE")) - getSession().close(); - else - getRemote().sendString(message); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java b/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java deleted file mode 100644 index 55c54970e4..0000000000 --- a/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.websocket; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpsServer; -import static org.testng.Assert.assertEquals; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ProxyServer; -import org.eclipse.jetty.proxy.ConnectHandler; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.websocket.server.WebSocketHandler; -import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.Test; - -/** - * Proxy usage tests. - */ -public abstract class ProxyTunnellingTest extends AbstractBasicTest { - - private Server server2; - - public void setUpServers(boolean targetHttps) throws Exception { - port1 = findFreePort(); - server = newJettyHttpServer(port1); - server.setHandler(new ConnectHandler()); - server.start(); - - port2 = findFreePort(); - - server2 = targetHttps ? newJettyHttpsServer(port2) : newJettyHttpServer(port2); - server2.setHandler(getWebSocketHandler()); - server2.start(); - - logger.info("Local HTTP server started successfully"); - } - - @Override - public WebSocketHandler getWebSocketHandler() { - return new WebSocketHandler() { - @Override - public void configure(WebSocketServletFactory factory) { - factory.register(EchoSocket.class); - } - }; - } - - @AfterMethod(alwaysRun = true) - public void tearDownGlobal() throws Exception { - server.stop(); - server2.stop(); - } - - @Test(timeOut = 60000) - public void echoWSText() throws Exception { - runTest(false); - } - - @Test(timeOut = 60000) - public void echoWSSText() throws Exception { - runTest(true); - } - - private void runTest(boolean secure) throws Exception { - - setUpServers(secure); - - String targetUrl = String.format("%s://127.0.0.1:%d/", secure ? "wss" : "ws", port2); - - // CONNECT happens over HTTP, not HTTPS - ProxyServer ps = new ProxyServer(ProxyServer.Protocol.HTTP, "127.0.0.1", port1); - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setProxyServer(ps).setAcceptAnyCertificate(true).build(); - AsyncHttpClient asyncHttpClient = getAsyncHttpClient(config); - try { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference text = new AtomicReference(""); - - WebSocket websocket = asyncHttpClient.prepareGet(targetUrl).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { - - @Override - public void onMessage(String message) { - text.set(message); - latch.countDown(); - } - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - }).build()).get(); - - websocket.sendTextMessage("ECHO"); - - latch.await(); - assertEquals(text.get(), "ECHO"); - } finally { - asyncHttpClient.close(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/websocket/RedirectTest.java b/api/src/test/java/org/asynchttpclient/websocket/RedirectTest.java deleted file mode 100644 index b0b825fa86..0000000000 --- a/api/src/test/java/org/asynchttpclient/websocket/RedirectTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 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.websocket; - -import static org.asynchttpclient.async.util.TestUtils.addHttpConnector; -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.server.handler.HandlerList; -import org.eclipse.jetty.websocket.server.WebSocketHandler; -import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; - -public abstract class RedirectTest extends AbstractBasicTest { - - @BeforeClass - @Override - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - port2 = findFreePort(); - - server = newJettyHttpServer(port1); - addHttpConnector(server, port2); - - HandlerList list = new HandlerList(); - list.addHandler(new AbstractHandler() { - @Override - public void handle(String s, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException { - if (request.getLocalPort() == port2) { - httpServletResponse.sendRedirect(getTargetUrl()); - } - } - }); - list.addHandler(getWebSocketHandler()); - server.setHandler(list); - - server.start(); - logger.info("Local HTTP server started successfully"); - } - - @Override - public WebSocketHandler getWebSocketHandler() { - return new WebSocketHandler() { - @Override - public void configure(WebSocketServletFactory factory) { - factory.register(EchoSocket.class); - } - }; - } - - - @Test(timeOut = 60000) - public void testRedirectToWSResource() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build()); - try { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference text = new AtomicReference(""); - - WebSocket websocket = c.prepareGet(getRedirectURL()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { - - @Override - public void onOpen(WebSocket websocket) { - text.set("OnOpen"); - latch.countDown(); - } - - @Override - public void onClose(WebSocket websocket) { - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - }).build()).get(); - - latch.await(); - assertEquals(text.get(), "OnOpen"); - websocket.close(); - } finally { - c.close(); - } - } - - private String getRedirectURL() { - return String.format("ws://127.0.0.1:%d/", port2); - } -} diff --git a/api/src/test/java/org/asynchttpclient/websocket/TextMessageTest.java b/api/src/test/java/org/asynchttpclient/websocket/TextMessageTest.java deleted file mode 100644 index 31d740c5ad..0000000000 --- a/api/src/test/java/org/asynchttpclient/websocket/TextMessageTest.java +++ /dev/null @@ -1,396 +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.websocket; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.eclipse.jetty.websocket.server.WebSocketHandler; -import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.testng.annotations.Test; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; - -public abstract class TextMessageTest extends AbstractBasicTest { - - @Override - public WebSocketHandler getWebSocketHandler() { - return new WebSocketHandler() { - @Override - public void configure(WebSocketServletFactory factory) { - factory.register(EchoSocket.class); - } - }; - } - - @Test(timeOut = 60000) - public void onOpen() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference text = new AtomicReference(""); - - c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { - - @Override - public void onOpen(WebSocket websocket) { - text.set("OnOpen"); - latch.countDown(); - } - - @Override - public void onClose(WebSocket websocket) { - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - }).build()).get(); - - latch.await(); - assertEquals(text.get(), "OnOpen"); - } finally { - c.close(); - } - } - - @Test(timeOut = 60000) - public void onEmptyListenerTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - WebSocket websocket = null; - try { - websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().build()).get(); - } catch (Throwable t) { - fail(); - } - assertTrue(websocket != null); - } finally { - c.close(); - } - } - - @Test(timeOut = 60000) - public void onFailureTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - Throwable t = null; - try { - /* WebSocket websocket = */c.prepareGet("ws://abcdefg").execute(new WebSocketUpgradeHandler.Builder().build()).get(); - } catch (Throwable t2) { - t = t2; - } - assertTrue(t != null); - } finally { - c.close(); - } - } - - @Test(timeOut = 60000) - public void onTimeoutCloseTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference text = new AtomicReference(""); - - c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - text.set("OnClose"); - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - }).build()).get(); - - latch.await(); - assertEquals(text.get(), "OnClose"); - } finally { - c.close(); - } - } - - @Test(timeOut = 60000) - public void onClose() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference text = new AtomicReference(""); - - WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - text.set("OnClose"); - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - }).build()).get(); - - websocket.close(); - - latch.await(); - assertEquals(text.get(), "OnClose"); - } finally { - c.close(); - } - } - - @Test(timeOut = 60000) - public void echoText() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference text = new AtomicReference(""); - - WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { - - @Override - public void onMessage(String message) { - text.set(message); - latch.countDown(); - } - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - }).build()).get(); - - websocket.sendTextMessage("ECHO"); - - latch.await(); - assertEquals(text.get(), "ECHO"); - } finally { - c.close(); - } - } - - @Test(timeOut = 60000) - public void echoDoubleListenerText() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(2); - final AtomicReference text = new AtomicReference(""); - - WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { - - @Override - public void onMessage(String message) { - text.set(message); - latch.countDown(); - } - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - }).addWebSocketListener(new WebSocketTextListener() { - - @Override - public void onMessage(String message) { - text.set(text.get() + message); - latch.countDown(); - } - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - }).build()).get(); - - websocket.sendTextMessage("ECHO"); - - latch.await(); - assertEquals(text.get(), "ECHOECHO"); - } finally { - c.close(); - } - } - - @Test - public void echoTwoMessagesTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(2); - final AtomicReference text = new AtomicReference(""); - - /* WebSocket websocket = */c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { - - @Override - public void onMessage(String message) { - text.set(text.get() + message); - latch.countDown(); - } - - @Override - public void onOpen(WebSocket websocket) { - websocket.sendTextMessage("ECHO").sendTextMessage("ECHO"); - } - - @Override - public void onClose(WebSocket websocket) { - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - }).build()).get(); - - latch.await(); - assertEquals(text.get(), "ECHOECHO"); - } finally { - c.close(); - } - } - - public void echoFragments() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference text = new AtomicReference(""); - - WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { - - @Override - public void onMessage(String message) { - text.set(message); - latch.countDown(); - } - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - latch.countDown(); - } - }).build()).get(); - - websocket.streamText("ECHO", false); - websocket.streamText("ECHO", true); - - latch.await(); - assertEquals(text.get(), "ECHOECHO"); - } finally { - c.close(); - } - } - - @Test(timeOut = 60000) - public void echoTextAndThenClose() throws Throwable { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - final CountDownLatch textLatch = new CountDownLatch(1); - final CountDownLatch closeLatch = new CountDownLatch(1); - final AtomicReference text = new AtomicReference(""); - - final WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { - - @Override - public void onMessage(String message) { - text.set(text.get() + message); - textLatch.countDown(); - } - - @Override - public void onOpen(WebSocket websocket) { - } - - @Override - public void onClose(WebSocket websocket) { - closeLatch.countDown(); - } - - @Override - public void onError(Throwable t) { - t.printStackTrace(); - closeLatch.countDown(); - } - }).build()).get(); - - websocket.sendTextMessage("ECHO"); - textLatch.await(); - - websocket.sendTextMessage("CLOSE"); - closeLatch.await(); - - assertEquals(text.get(), "ECHO"); - } finally { - c.close(); - } - } -} diff --git a/api/src/test/resources/logback-test.xml b/api/src/test/resources/logback-test.xml deleted file mode 100644 index 4acf278710..0000000000 --- a/api/src/test/resources/logback-test.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - %d [%thread] %level %logger - %m%n - - - - - - - - - \ No newline at end of file diff --git a/client/pom.xml b/client/pom.xml new file mode 100644 index 0000000000..733f20b517 --- /dev/null +++ b/client/pom.xml @@ -0,0 +1,192 @@ + + + + + org.asynchttpclient + async-http-client-project + 3.0.2 + + + 4.0.0 + async-http-client + AHC/Client + The Async Http Client (AHC) classes. + + + org.asynchttpclient.client + + 11.0.24 + 10.1.40 + 2.18.0 + 4.11.0 + 3.0 + 2.1.0 + + + + + hyperxpro + Aayush Atharva + aayush@shieldblaze.com + + + + + + + commons-fileupload + commons-fileupload + 1.5 + test + + + + + javax.portlet + portlet-api + 3.0.1 + test + + + + org.apache.kerby + kerb-simplekdc + ${kerby.version} + test + + + org.jboss.xnio + xnio-api + + + + + + ch.qos.logback + logback-classic + ${logback.version} + test + + + + + org.junit.jupiter + junit-jupiter-api + test + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + org.junit.jupiter + junit-jupiter-params + test + + + + io.github.nettyplus + netty-leak-detector-junit-extension + test + + + + org.eclipse.jetty + jetty-servlet + ${jetty.version} + test + + + + org.eclipse.jetty + jetty-servlets + ${jetty.version} + test + + + + org.eclipse.jetty + jetty-security + ${jetty.version} + test + + + + org.eclipse.jetty + jetty-proxy + ${jetty.version} + test + + + + + org.eclipse.jetty.websocket + websocket-jetty-server + ${jetty.version} + test + + + + org.eclipse.jetty.websocket + websocket-servlet + ${jetty.version} + test + + + + org.apache.tomcat.embed + tomcat-embed-core + ${tomcat.version} + test + + + + commons-io + commons-io + ${commons-io.version} + test + + + + org.mockito + mockito-core + ${mockito.version} + test + + + + org.hamcrest + hamcrest + ${hamcrest.version} + test + + + + + io.github.artsok + rerunner-jupiter + 2.1.6 + test + + + diff --git a/client/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java b/client/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java new file mode 100644 index 0000000000..63335cb29a --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java @@ -0,0 +1,126 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.netty.handler.codec.http.HttpHeaders; +import org.asynchttpclient.handler.ProgressAsyncHandler; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.InputStream; +import java.util.concurrent.Future; + +/** + * An {@link AsyncHandler} augmented with an {@link #onCompleted(Response)} + * convenience method which gets called when the {@link Response} processing is + * finished. This class also implements the {@link ProgressAsyncHandler} + * callback, all doing nothing except returning + * {@link AsyncHandler.State#CONTINUE} + * + * @param Type of the value that will be returned by the associated + * {@link Future} + */ +public abstract class AsyncCompletionHandler implements ProgressAsyncHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AsyncCompletionHandler.class); + private final Response.ResponseBuilder builder = new Response.ResponseBuilder(); + + @Override + public State onStatusReceived(HttpResponseStatus status) throws Exception { + builder.reset(); + builder.accumulate(status); + return State.CONTINUE; + } + + @Override + public State onHeadersReceived(HttpHeaders headers) throws Exception { + builder.accumulate(headers); + return State.CONTINUE; + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart content) throws Exception { + builder.accumulate(content); + return State.CONTINUE; + } + + @Override + public State onTrailingHeadersReceived(HttpHeaders headers) throws Exception { + builder.accumulate(headers); + return State.CONTINUE; + } + + @Override + public final @Nullable T onCompleted() throws Exception { + return onCompleted(builder.build()); + } + + @Override + public void onThrowable(Throwable t) { + LOGGER.debug(t.getMessage(), t); + } + + /** + * Invoked once the HTTP response processing is finished. + * + * @param response The {@link Response} + * @return T Value that will be returned by the associated + * {@link Future} + * @throws Exception if something wrong happens + */ + public abstract @Nullable T onCompleted(@Nullable Response response) throws Exception; + + /** + * Invoked when the HTTP headers have been fully written on the I/O socket. + * + * @return a {@link AsyncHandler.State} telling to CONTINUE + * or ABORT the current processing. + */ + @Override + public State onHeadersWritten() { + return State.CONTINUE; + } + + /** + * 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 + * or ABORT the current processing. + */ + @Override + public State onContentWritten() { + return State.CONTINUE; + } + + /** + * Invoked when the I/O operation associated with the {@link Request} body as + * been progressed. + * + * @param amount The amount of bytes to transfer + * @param current The amount of bytes transferred + * @param total The total number of bytes transferred + * @return a {@link AsyncHandler.State} telling to CONTINUE + * or ABORT the current processing. + */ + @Override + public State onContentWriteProgress(long amount, long current, long total) { + return State.CONTINUE; + } +} diff --git a/client/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java b/client/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java new file mode 100644 index 0000000000..25fc9da185 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java @@ -0,0 +1,31 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 org.jetbrains.annotations.Nullable; + +/** + * 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 new file mode 100644 index 0000000000..22451fe097 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/AsyncHandler.java @@ -0,0 +1,256 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.netty.channel.Channel; +import io.netty.handler.codec.http.HttpHeaders; +import org.asynchttpclient.netty.request.NettyRequest; +import org.jetbrains.annotations.Nullable; + +import javax.net.ssl.SSLSession; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.concurrent.Future; + + +/** + * An asynchronous handler or callback which gets invoked as soon as some data is available when + * processing an asynchronous response. + *
+ * Callback methods get invoked in the following order: + *

    + *
  1. {@link #onStatusReceived(HttpResponseStatus)},
  2. + *
  3. {@link #onHeadersReceived(HttpHeaders)},
  4. + *
  5. {@link #onBodyPartReceived(HttpResponseBodyPart)}, which could be invoked multiple times,
  6. + *
  7. {@link #onTrailingHeadersReceived(HttpHeaders)}, which is only invoked if trailing HTTP headers are received
  8. + *
  9. {@link #onCompleted()}, once the response has been fully read.
  10. + *
+ *
+ * Returning a {@link AsyncHandler.State#ABORT} from any of those callback methods will interrupt asynchronous response + * processing. After that, only {@link #onCompleted()} is going to be called. + *
+ * AsyncHandlers aren't thread safe. Hence, you should avoid re-using the same instance when doing concurrent requests. + * As an example, the following may produce unexpected results: + *
+ *   AsyncHandler ah = new AsyncHandler() {....};
+ *   AsyncHttpClient client = new AsyncHttpClient();
+ *   client.prepareGet("http://.../").execute(ah);
+ *   client.prepareGet("http://.../").execute(ah);
+ * 
+ * It is recommended to create a new instance instead. + *

+ * 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 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} + */ +public interface AsyncHandler { + + /** + * Invoked as soon as the HTTP status line has been received + * + * @param responseStatus the status code and test of the response + * @return a {@link State} telling to CONTINUE or ABORT the current processing. + * @throws Exception if something wrong happens + */ + State onStatusReceived(HttpResponseStatus responseStatus) throws Exception; + + /** + * Invoked as soon as the HTTP headers have been received. + * + * @param headers the HTTP headers. + * @return a {@link State} telling to CONTINUE or ABORT the current processing. + * @throws Exception if something wrong happens + */ + State onHeadersReceived(HttpHeaders headers) throws Exception; + + /** + * Invoked as soon as some response body part are received. Could be invoked many times. + * Beware that, depending on the provider (Netty) this can be notified with empty body parts. + * + * @param bodyPart response's body part. + * @return a {@link State} telling to CONTINUE or ABORT the current processing. Aborting will also close the connection. + * @throws Exception if something wrong happens + */ + State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception; + + /** + * Invoked when trailing headers have been received. + * + * @param headers the trailing HTTP headers. + * @return a {@link State} telling to CONTINUE or ABORT the current processing. + * @throws Exception if something wrong happens + */ + default State onTrailingHeadersReceived(HttpHeaders headers) throws Exception { + return State.CONTINUE; + } + + /** + * Invoked when an unexpected exception occurs during the processing of the response. The exception may have been + * produced by implementation of onXXXReceived method invocation. + * + * @param t a {@link Throwable} + */ + void onThrowable(Throwable t); + + /** + * Invoked once the HTTP response processing is finished. + *
+ * Gets always invoked as last callback method. + * + * @return T Value that will be returned by the associated {@link Future} + * @throws Exception if something wrong happens + */ + @Nullable + T onCompleted() throws Exception; + + /** + * Notify the callback before hostname resolution + * + * @param name the name to be resolved + */ + default void onHostnameResolutionAttempt(String name) { + } + + // ////////// DNS ///////////////// + + /** + * Notify the callback after hostname resolution was successful. + * + * @param name the name to be resolved + * @param addresses the resolved addresses + */ + default void onHostnameResolutionSuccess(String name, List addresses) { + } + + /** + * Notify the callback after hostname resolution failed. + * + * @param name the name to be resolved + * @param cause the failure cause + */ + default void onHostnameResolutionFailure(String name, Throwable cause) { + } + + // ////////////// TCP CONNECT //////// + + /** + * 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). + * + * @param remoteAddress the address we try to connect to + */ + default void onTcpConnectAttempt(InetSocketAddress remoteAddress) { + } + + /** + * Notify the callback after a successful connect + * + * @param remoteAddress the address we try to connect to + * @param connection the connection + */ + default void onTcpConnectSuccess(InetSocketAddress remoteAddress, Channel connection) { + } + + /** + * Notify the callback after a failed connect. + *

+ * Might be called several times, or be followed by onTcpConnectSuccess when the name was resolved to multiple addresses. + * + * @param remoteAddress the address we try to connect to + * @param cause the cause of the failure + */ + default void onTcpConnectFailure(InetSocketAddress remoteAddress, Throwable cause) { + } + + // ////////////// TLS /////////////// + + /** + * Notify the callback before TLS handshake + */ + default void onTlsHandshakeAttempt() { + } + + /** + * Notify the callback after the TLS was successful + */ + default void onTlsHandshakeSuccess(SSLSession sslSession) { + } + + /** + * Notify the callback after the TLS failed + * + * @param cause the cause of the failure + */ + default void onTlsHandshakeFailure(Throwable cause) { + } + + // /////////// POOLING ///////////// + + /** + * Notify the callback when trying to fetch a connection from the pool. + */ + default void onConnectionPoolAttempt() { + } + + /** + * Notify the callback when a new connection was successfully fetched from the pool. + * + * @param connection the connection + */ + default void onConnectionPooled(Channel connection) { + } + + /** + * Notify the callback when trying to offer a connection to the pool. + * + * @param connection the connection + */ + default void onConnectionOffer(Channel connection) { + } + + // //////////// SENDING ////////////// + + /** + * Notify the callback when a request is being written on the channel. If the original request causes multiple requests to be sent, for example, because of authorization or + * retry, it will be notified multiple times. + * + * @param request the real request object as passed to the provider + */ + default void onRequestSend(NettyRequest request) { + } + + /** + * Notify the callback every time a request is being retried. + */ + default void onRetry() { + } + + enum State { + + /** + * Stop the processing. + */ + ABORT, + /** + * Continue the processing + */ + CONTINUE + } +} diff --git a/client/src/main/java/org/asynchttpclient/AsyncHttpClient.java b/client/src/main/java/org/asynchttpclient/AsyncHttpClient.java new file mode 100755 index 0000000000..01a3ecf734 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/AsyncHttpClient.java @@ -0,0 +1,302 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 java.io.Closeable; +import java.util.concurrent.Future; +import java.util.function.Predicate; + +/** + * This class support asynchronous and synchronous HTTP requests. + *
+ * To execute a synchronous HTTP request, you just need to do + *

+ *    AsyncHttpClient c = new AsyncHttpClient();
+ *    Future<Response> f = c.prepareGet(TARGET_URL).execute();
+ * 
+ *
+ * The code above will block until the response is fully received. To execute an asynchronous HTTP request, you + * create an {@link AsyncHandler} or its abstract implementation, {@link AsyncCompletionHandler} + *
+ *
+ *       AsyncHttpClient c = new AsyncHttpClient();
+ *       Future<Response> f = c.prepareGet(TARGET_URL).execute(new AsyncCompletionHandler<Response>() {
+ *
+ *          @Override
+ *          public Response onCompleted(Response response) throws IOException {
+ *               // Do something
+ *              return response;
+ *          }
+ *
+ *          @Override
+ *          public void onThrowable(Throwable t) {
+ *          }
+ *      });
+ *      Response response = f.get();
+ *
+ *      // We are just interested in retrieving the status code.
+ *     Future<Integer> f = c.prepareGet(TARGET_URL).execute(new AsyncCompletionHandler<Integer>() {
+ *
+ *          @Override
+ *          public Integer onCompleted(Response response) throws IOException {
+ *               // Do something
+ *              return response.getStatusCode();
+ *          }
+ *
+ *          @Override
+ *          public void onThrowable(Throwable t) {
+ *          }
+ *      });
+ *      Integer statusCode = f.get();
+ * 
+ * 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 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>() {
+ *          private StringBuilder builder = new StringBuilder();
+ *
+ *          @Override
+ *          public STATE onStatusReceived(HttpResponseStatus s) throws Exception {
+ *               // return STATE.CONTINUE or STATE.ABORT
+ *               return STATE.CONTINUE
+ *          }
+ *
+ *          @Override
+ *          public STATE onHeadersReceived(HttpResponseHeaders bodyPart) throws Exception {
+ *               // return STATE.CONTINUE or STATE.ABORT
+ *               return STATE.CONTINUE
+ *
+ *          }
+ *          @Override
+ *
+ *          public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception {
+ *               builder.append(new String(bodyPart));
+ *               // return STATE.CONTINUE or STATE.ABORT
+ *               return STATE.CONTINUE
+ *          }
+ *
+ *          @Override
+ *          public String onCompleted() throws Exception {
+ *               // Will be invoked once the response has been fully read or a ResponseComplete exception
+ *               // has been thrown.
+ *               return builder.toString();
+ *          }
+ *
+ *          @Override
+ *          public void onThrowable(Throwable t) {
+ *          }
+ *      });
+ *
+ *      String bodyResponse = f.get();
+ * 
+ * You can asynchronously process the response status, headers and body and decide when to + * stop processing the response by returning a new {@link AsyncHandler.State#ABORT} at any moment. + *

+ * This class can also be used without the need of {@link AsyncHandler}. + *
+ *

+ *      AsyncHttpClient c = new AsyncHttpClient();
+ *      Future<Response> f = c.prepareGet(TARGET_URL).execute();
+ *      Response r = f.get();
+ * 
+ *

+ * Finally, you can configure the AsyncHttpClient using an {@link DefaultAsyncHttpClientConfig} instance. + *
+ *

+ *      AsyncHttpClient c = new AsyncHttpClient(new DefaultAsyncHttpClientConfig.Builder().setRequestTimeout(...).build());
+ *      Future<Response> f = c.prepareGet(TARGET_URL).execute();
+ *      Response r = f.get();
+ * 
+ *
+ * An instance of this class will cache every HTTP 1.1 connection and close them when the {@link DefaultAsyncHttpClientConfig#getReadTimeout()} + * expires. This object can hold many persistent connections to different hosts. + */ +public interface AsyncHttpClient extends Closeable { + + /** + * Return true if closed + * + * @return true if closed + */ + boolean isClosed(); + + /** + * Set default signature calculator to use for requests built by this client instance + * + * @param signatureCalculator a signature calculator + * @return {@link RequestBuilder} + */ + AsyncHttpClient setSignatureCalculator(SignatureCalculator signatureCalculator); + + /** + * Prepare an HTTP client request. + * + * @param method HTTP request method type. MUST BE in upper case + * @param url A well-formed URL. + * @return {@link RequestBuilder} + */ + BoundRequestBuilder prepare(String method, String url); + + + /** + * Prepare an HTTP client GET request. + * + * @param url A well-formed URL. + * @return {@link RequestBuilder} + */ + BoundRequestBuilder prepareGet(String url); + + /** + * Prepare an HTTP client CONNECT request. + * + * @param url A well-formed URL. + * @return {@link RequestBuilder} + */ + BoundRequestBuilder prepareConnect(String url); + + /** + * Prepare an HTTP client OPTIONS request. + * + * @param url A well-formed URL. + * @return {@link RequestBuilder} + */ + BoundRequestBuilder prepareOptions(String url); + + /** + * Prepare an HTTP client HEAD request. + * + * @param url A well-formed URL. + * @return {@link RequestBuilder} + */ + BoundRequestBuilder prepareHead(String url); + + /** + * Prepare an HTTP client POST request. + * + * @param url A well-formed URL. + * @return {@link RequestBuilder} + */ + BoundRequestBuilder preparePost(String url); + + /** + * Prepare an HTTP client PUT request. + * + * @param url A well-formed URL. + * @return {@link RequestBuilder} + */ + BoundRequestBuilder preparePut(String url); + + /** + * Prepare an HTTP client DELETE request. + * + * @param url A well-formed URL. + * @return {@link RequestBuilder} + */ + BoundRequestBuilder prepareDelete(String url); + + /** + * Prepare an HTTP client PATCH request. + * + * @param url A well-formed URL. + * @return {@link RequestBuilder} + */ + BoundRequestBuilder preparePatch(String url); + + /** + * Prepare an HTTP client TRACE request. + * + * @param url A well-formed URL. + * @return {@link RequestBuilder} + */ + BoundRequestBuilder prepareTrace(String url); + + /** + * Construct a {@link RequestBuilder} using a {@link Request} + * + * @param request a {@link Request} + * @return {@link RequestBuilder} + */ + BoundRequestBuilder prepareRequest(Request request); + + /** + * Construct a {@link RequestBuilder} using a {@link RequestBuilder} + * + * @param requestBuilder a {@link RequestBuilder} + * @return {@link RequestBuilder} + */ + BoundRequestBuilder prepareRequest(RequestBuilder requestBuilder); + + /** + * Execute an HTTP request. + * + * @param request {@link Request} + * @param handler an instance of {@link AsyncHandler} + * @param Type of the value that will be returned by the associated {@link Future} + * @return a {@link Future} of type T + */ + ListenableFuture executeRequest(Request request, AsyncHandler handler); + + /** + * Execute an HTTP request. + * + * @param requestBuilder {@link RequestBuilder} + * @param handler an instance of {@link AsyncHandler} + * @param Type of the value that will be returned by the associated {@link Future} + * @return a {@link Future} of type T + */ + ListenableFuture executeRequest(RequestBuilder requestBuilder, AsyncHandler handler); + + /** + * Execute an HTTP request. + * + * @param request {@link Request} + * @return a {@link Future} of type Response + */ + ListenableFuture executeRequest(Request request); + + /** + * Execute an HTTP request. + * + * @param requestBuilder {@link RequestBuilder} + * @return a {@link Future} of type Response + */ + ListenableFuture executeRequest(RequestBuilder requestBuilder); + + /*** + * Return details about pooled connections. + * + * @return a {@link ClientStats} + */ + ClientStats getClientStats(); + + /** + * Flush ChannelPool partitions based on a predicate + * + * @param predicate the predicate + */ + void flushChannelPoolPartitions(Predicate predicate); + + /** + * Return the config associated to this client. + * + * @return the config associated to this client. + */ + AsyncHttpClientConfig getConfig(); +} diff --git a/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java new file mode 100644 index 0000000000..954628b3d4 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -0,0 +1,403 @@ +/* + * 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; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.handler.ssl.SslContext; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; +import org.asynchttpclient.channel.ChannelPool; +import org.asynchttpclient.channel.KeepAliveStrategy; +import org.asynchttpclient.cookie.CookieStore; +import org.asynchttpclient.filter.IOExceptionFilter; +import org.asynchttpclient.filter.RequestFilter; +import org.asynchttpclient.filter.ResponseFilter; +import org.asynchttpclient.netty.EagerResponseBodyPart; +import org.asynchttpclient.netty.LazyResponseBodyPart; +import org.asynchttpclient.netty.channel.ConnectionSemaphoreFactory; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.proxy.ProxyServerSelector; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadFactory; +import java.util.function.Consumer; + +public interface AsyncHttpClientConfig { + + /** + * @return the version of AHC + */ + String getAhcVersion(); + + /** + * Return the name of {@link AsyncHttpClient}, which is used for thread naming and debugging. + * + * @return the name. + */ + String getThreadPoolName(); + + /** + * Return the maximum number of connections an {@link AsyncHttpClient} can handle. + * + * @return the maximum number of connections an {@link AsyncHttpClient} can handle. + */ + int getMaxConnections(); + + /** + * Return the maximum number of connections per hosts an {@link AsyncHttpClient} can handle. + * + * @return the maximum number of connections per host an {@link AsyncHttpClient} can handle. + */ + int getMaxConnectionsPerHost(); + + /** + * Return the maximum duration in milliseconds an {@link AsyncHttpClient} can wait to acquire a free channel + * + * @return Return the maximum duration in milliseconds an {@link AsyncHttpClient} can wait to acquire a free channel + */ + int getAcquireFreeChannelTimeout(); + + + /** + * Return the maximum time an {@link AsyncHttpClient} can wait when connecting to a remote host + * + * @return the maximum time an {@link AsyncHttpClient} can wait when connecting to a remote host + */ + Duration getConnectTimeout(); + + /** + * Return the maximum time an {@link AsyncHttpClient} can stay idle. + * + * @return the maximum time an {@link AsyncHttpClient} can stay idle. + */ + Duration getReadTimeout(); + + /** + * Return the maximum time an {@link AsyncHttpClient} will keep connection in pool. + * + * @return the maximum time an {@link AsyncHttpClient} will keep connection in pool. + */ + Duration getPooledConnectionIdleTimeout(); + + /** + * @return the period to clean the pool of dead and idle connections. + */ + Duration getConnectionPoolCleanerPeriod(); + + /** + * Return the maximum time an {@link AsyncHttpClient} waits until the response is completed. + * + * @return the maximum time an {@link AsyncHttpClient} waits until the response is completed. + */ + Duration getRequestTimeout(); + + /** + * Is HTTP redirect enabled + * + * @return true if enabled. + */ + boolean isFollowRedirect(); + + /** + * Get the maximum number of HTTP redirect + * + * @return the maximum number of HTTP redirect + */ + int getMaxRedirects(); + + /** + * Is the {@link ChannelPool} support enabled. + * + * @return true if keep-alive is enabled + */ + boolean isKeepAlive(); + + /** + * Return the USER_AGENT header value + * + * @return the USER_AGENT header value + */ + String getUserAgent(); + + /** + * Is HTTP compression enforced. + * + * @return true if compression is enforced + */ + boolean isCompressionEnforced(); + + /** + * If automatic content decompression is enabled. + * + * @return true if content decompression is enabled + */ + boolean isEnableAutomaticDecompression(); + + /** + * Return the {@link ThreadFactory} an {@link AsyncHttpClient} use for handling asynchronous response. + * + * @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(); + + /** + * An instance of {@link ProxyServer} used by an {@link AsyncHttpClient} + * + * @return instance of {@link ProxyServer} + */ + ProxyServerSelector getProxyServerSelector(); + + /** + * Return an instance of {@link SslContext} used for SSL connection. + * + * @return an instance of {@link SslContext} used for SSL connection. + */ + @Nullable + SslContext getSslContext(); + + /** + * Return the current {@link Realm} + * + * @return the current {@link Realm} + */ + @Nullable + Realm getRealm(); + + /** + * Return the list of {@link RequestFilter} + * + * @return Unmodifiable list of {@link RequestFilter} + */ + List getRequestFilters(); + + /** + * Return the list of {@link ResponseFilter} + * + * @return Unmodifiable list of {@link ResponseFilter} + */ + List getResponseFilters(); + + /** + * Return the list of {@link IOException} + * + * @return Unmodifiable list of {@link IOException} + */ + List getIoExceptionFilters(); + + /** + * Return cookie store that is used to store and retrieve cookies + * + * @return {@link CookieStore} object + */ + CookieStore getCookieStore(); + + /** + * Return the delay in milliseconds to evict expired cookies from {@linkplain CookieStore} + * + * @return the delay in milliseconds to evict expired cookies from {@linkplain CookieStore} + */ + int expiredCookieEvictionDelay(); + + /** + * Return the number of time the library will retry when an {@link IOException} is throw by the remote server + * + * @return the number of time the library will retry when an {@link IOException} is throw by the remote server + */ + int getMaxRequestRetry(); + + /** + * @return the disableUrlEncodingForBoundRequests + */ + boolean isDisableUrlEncodingForBoundRequests(); + + /** + * @return true if AHC is to use a LAX cookie encoder, e.g. accept illegal chars in cookie value + */ + boolean isUseLaxCookieEncoder(); + + /** + * In the case of a POST/Redirect/Get scenario where the server uses a 302 for the redirect, should AHC respond to the redirect with a GET or whatever the original method was. + * Unless configured otherwise, for a 302, AHC, will use a GET for this case. + * + * @return {@code true} if strict 302 handling is to be used, otherwise {@code false}. + */ + boolean isStrict302Handling(); + + /** + * @return the maximum time an {@link AsyncHttpClient} will keep connection in the pool, or negative value to keep connection while possible. + */ + Duration getConnectionTtl(); + + boolean isUseOpenSsl(); + + boolean isUseInsecureTrustManager(); + + /** + * @return true to disable all HTTPS behaviors AT ONCE, such as hostname verification and SNI + */ + boolean isDisableHttpsEndpointIdentificationAlgorithm(); + + /** + * @return the array of enabled protocols + */ + @Nullable + String[] getEnabledProtocols(); + + /** + * @return the array of enabled cipher suites + */ + @Nullable + String[] getEnabledCipherSuites(); + + /** + * @return if insecure cipher suites must be filtered out (only used when not explicitly passing enabled cipher suites) + */ + boolean isFilterInsecureCipherSuites(); + + /** + * @return the size of the SSL session cache, 0 means using the default value + */ + int getSslSessionCacheSize(); + + /** + * @return the SSL session timeout in seconds, 0 means using the default value + */ + int getSslSessionTimeout(); + + int getHttpClientCodecMaxInitialLineLength(); + + int getHttpClientCodecMaxHeaderSize(); + + int getHttpClientCodecMaxChunkSize(); + + int getHttpClientCodecInitialBufferSize(); + + boolean isDisableZeroCopy(); + + int getHandshakeTimeout(); + + @Nullable + SslEngineFactory getSslEngineFactory(); + + int getChunkedFileChunkSize(); + + int getWebSocketMaxBufferSize(); + + int getWebSocketMaxFrameSize(); + + boolean isKeepEncodingHeader(); + + Duration getShutdownQuietPeriod(); + + Duration getShutdownTimeout(); + + Map, Object> getChannelOptions(); + + @Nullable + EventLoopGroup getEventLoopGroup(); + + boolean isUseNativeTransport(); + + boolean isUseOnlyEpollNativeTransport(); + + @Nullable + Consumer getHttpAdditionalChannelInitializer(); + + @Nullable + Consumer getWsAdditionalChannelInitializer(); + + ResponseBodyPartFactory getResponseBodyPartFactory(); + + @Nullable + ChannelPool getChannelPool(); + + @Nullable + ConnectionSemaphoreFactory getConnectionSemaphoreFactory(); + + @Nullable + Timer getNettyTimer(); + + /** + * @return the duration between tick of {@link HashedWheelTimer} + */ + long getHashedWheelTimerTickDuration(); + + /** + * @return the size of the hashed wheel {@link HashedWheelTimer} + */ + int getHashedWheelTimerSize(); + + KeepAliveStrategy getKeepAliveStrategy(); + + boolean isValidateResponseHeaders(); + + boolean isAggregateWebSocketFrameFragments(); + + boolean isEnableWebSocketCompression(); + + boolean isTcpNoDelay(); + + boolean isSoReuseAddress(); + + boolean isSoKeepAlive(); + + int getSoLinger(); + + int getSoSndBuf(); + + int getSoRcvBuf(); + + @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 { + @Override + public HttpResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { + return new EagerResponseBodyPart(buf, last); + } + }, + + LAZY { + @Override + public HttpResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { + return new LazyResponseBodyPart(buf, last); + } + }; + + public abstract HttpResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last); + } +} diff --git a/client/src/main/java/org/asynchttpclient/AsyncHttpClientState.java b/client/src/main/java/org/asynchttpclient/AsyncHttpClientState.java new file mode 100644 index 0000000000..5916d69f0c --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/AsyncHttpClientState.java @@ -0,0 +1,31 @@ +/* + * 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; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class AsyncHttpClientState { + + private final AtomicBoolean closed; + + AsyncHttpClientState(AtomicBoolean closed) { + this.closed = closed; + } + + public boolean isClosed() { + return closed.get(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/BoundRequestBuilder.java b/client/src/main/java/org/asynchttpclient/BoundRequestBuilder.java new file mode 100644 index 0000000000..99b3cc5d06 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/BoundRequestBuilder.java @@ -0,0 +1,44 @@ +/* + * 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. + * 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; + +public class BoundRequestBuilder extends RequestBuilderBase { + + private final AsyncHttpClient client; + + public BoundRequestBuilder(AsyncHttpClient client, String method, boolean isDisableUrlEncoding, boolean validateHeaders) { + super(method, isDisableUrlEncoding, validateHeaders); + this.client = client; + } + + public BoundRequestBuilder(AsyncHttpClient client, String method, boolean isDisableUrlEncoding) { + super(method, isDisableUrlEncoding); + this.client = client; + } + + public BoundRequestBuilder(AsyncHttpClient client, Request prototype) { + super(prototype); + this.client = client; + } + + public ListenableFuture execute(AsyncHandler handler) { + return client.executeRequest(build(), handler); + } + + public ListenableFuture execute() { + return client.executeRequest(build(), new AsyncCompletionHandlerBase()); + } +} diff --git a/client/src/main/java/org/asynchttpclient/ClientStats.java b/client/src/main/java/org/asynchttpclient/ClientStats.java new file mode 100644 index 0000000000..eef529221d --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/ClientStats.java @@ -0,0 +1,98 @@ +/* + * 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. + * 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 java.util.Collections; +import java.util.Map; +import java.util.Objects; + +/** + * A record class representing the state of a (@link org.asynchttpclient.AsyncHttpClient). + */ +public class ClientStats { + + private final Map statsPerHost; + + public ClientStats(Map statsPerHost) { + this.statsPerHost = Collections.unmodifiableMap(statsPerHost); + } + + /** + * @return A map from hostname to statistics on that host's connections. + * The returned map is unmodifiable. + */ + public Map getStatsPerHost() { + return statsPerHost; + } + + /** + * @return The sum of {@link #getTotalActiveConnectionCount()} and {@link #getTotalIdleConnectionCount()}, + * a long representing the total number of connections in the connection pool. + */ + public long getTotalConnectionCount() { + return statsPerHost + .values() + .stream() + .mapToLong(HostStats::getHostConnectionCount) + .sum(); + } + + /** + * @return A long representing the number of active connections in the connection pool. + */ + public long getTotalActiveConnectionCount() { + return statsPerHost + .values() + .stream() + .mapToLong(HostStats::getHostActiveConnectionCount) + .sum(); + } + + /** + * @return A long representing the number of idle connections in the connection pool. + */ + public long getTotalIdleConnectionCount() { + return statsPerHost + .values() + .stream() + .mapToLong(HostStats::getHostIdleConnectionCount) + .sum(); + } + + @Override + public String toString() { + return "There are " + getTotalConnectionCount() + + " total connections, " + getTotalActiveConnectionCount() + + " are active and " + getTotalIdleConnectionCount() + " are idle."; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ClientStats that = (ClientStats) o; + return Objects.equals(statsPerHost, that.statsPerHost); + } + + @Override + public int hashCode() { + return Objects.hashCode(statsPerHost); + } +} diff --git a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java new file mode 100644 index 0000000000..3b417a5a39 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java @@ -0,0 +1,344 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.netty.channel.EventLoopGroup; +import io.netty.handler.codec.http.cookie.Cookie; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; +import io.netty.util.concurrent.DefaultThreadFactory; +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.RequestFilter; +import org.asynchttpclient.handler.resumable.ResumableAsyncHandler; +import org.asynchttpclient.netty.channel.ChannelManager; +import org.asynchttpclient.netty.request.NettyRequestSender; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; + +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; +import static org.asynchttpclient.util.HttpConstants.Methods.HEAD; +import static org.asynchttpclient.util.HttpConstants.Methods.OPTIONS; +import static org.asynchttpclient.util.HttpConstants.Methods.PATCH; +import static org.asynchttpclient.util.HttpConstants.Methods.POST; +import static org.asynchttpclient.util.HttpConstants.Methods.PUT; +import static org.asynchttpclient.util.HttpConstants.Methods.TRACE; + +/** + * Default and threadsafe implementation of {@link AsyncHttpClient}. + */ +public class DefaultAsyncHttpClient implements AsyncHttpClient { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultAsyncHttpClient.class); + private final AsyncHttpClientConfig config; + private final boolean noRequestFilters; + private final AtomicBoolean closed = new AtomicBoolean(false); + private final ChannelManager channelManager; + private final NettyRequestSender requestSender; + private final boolean allowStopNettyTimer; + private final Timer nettyTimer; + + /** + * Default signature calculator to use for all requests constructed by this + * client instance. + */ + private @Nullable SignatureCalculator signatureCalculator; + + /** + * Create a new HTTP Asynchronous Client using the default + * {@link DefaultAsyncHttpClientConfig} configuration. The default + * {@link AsyncHttpClient} that will be used will be based on the classpath + * configuration. + *

+ * If none of those providers are found, then the engine will throw an + * IllegalStateException. + */ + public DefaultAsyncHttpClient() { + this(new DefaultAsyncHttpClientConfig.Builder().build()); + } + + /** + * Create a new HTTP Asynchronous Client using the specified + * {@link DefaultAsyncHttpClientConfig} configuration. This configuration + * will be passed to the default {@link AsyncHttpClient} that will be + * selected based on the classpath configuration. + * + * @param config a {@link DefaultAsyncHttpClientConfig} + */ + public DefaultAsyncHttpClient(AsyncHttpClientConfig config) { + + this.config = config; + noRequestFilters = config.getRequestFilters().isEmpty(); + final Timer configTimer = config.getNettyTimer(); + if (configTimer == null) { + allowStopNettyTimer = true; + nettyTimer = newNettyTimer(config); + } else { + allowStopNettyTimer = false; + nettyTimer = configTimer; + } + + 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); + } + } + } + + // Visible for testing + ChannelManager channelManager() { + return channelManager; + } + + private static Timer newNettyTimer(AsyncHttpClientConfig config) { + ThreadFactory threadFactory = config.getThreadFactory() != null ? config.getThreadFactory() : new DefaultThreadFactory(config.getThreadPoolName() + "-timer"); + HashedWheelTimer timer = new HashedWheelTimer(threadFactory, config.getHashedWheelTimerTickDuration(), TimeUnit.MILLISECONDS, config.getHashedWheelTimerSize()); + timer.start(); + return timer; + } + + @Override + public void close() { + if (closed.compareAndSet(false, true)) { + try { + channelManager.close(); + } catch (Throwable t) { + LOGGER.warn("Unexpected error on ChannelManager close", t); + } + CookieStore cookieStore = config.getCookieStore(); + if (cookieStore != null) { + cookieStore.decrementAndGet(); + } + if (allowStopNettyTimer) { + try { + nettyTimer.stop(); + } catch (Throwable t) { + LOGGER.warn("Unexpected error on HashedWheelTimer close", t); + } + } + } + } + + @Override + public boolean isClosed() { + return closed.get(); + } + + @Override + public DefaultAsyncHttpClient setSignatureCalculator(SignatureCalculator signatureCalculator) { + this.signatureCalculator = signatureCalculator; + return this; + } + + @Override + public BoundRequestBuilder prepare(String method, String url) { + return requestBuilder(method, url); + } + + @Override + public BoundRequestBuilder prepareGet(String url) { + return requestBuilder(GET, url); + } + + @Override + public BoundRequestBuilder prepareConnect(String url) { + return requestBuilder(CONNECT, url); + } + + @Override + public BoundRequestBuilder prepareOptions(String url) { + return requestBuilder(OPTIONS, url); + } + + @Override + public BoundRequestBuilder prepareHead(String url) { + return requestBuilder(HEAD, url); + } + + @Override + public BoundRequestBuilder preparePost(String url) { + return requestBuilder(POST, url); + } + + @Override + public BoundRequestBuilder preparePut(String url) { + return requestBuilder(PUT, url); + } + + @Override + public BoundRequestBuilder prepareDelete(String url) { + return requestBuilder(DELETE, url); + } + + @Override + public BoundRequestBuilder preparePatch(String url) { + return requestBuilder(PATCH, url); + } + + @Override + public BoundRequestBuilder prepareTrace(String url) { + return requestBuilder(TRACE, url); + } + + @Override + public BoundRequestBuilder prepareRequest(Request request) { + return requestBuilder(request); + } + + @Override + public BoundRequestBuilder prepareRequest(RequestBuilder requestBuilder) { + return prepareRequest(requestBuilder.build()); + } + + @Override + public ListenableFuture executeRequest(Request request, AsyncHandler handler) { + if (config.getCookieStore() != null) { + try { + List cookies = config.getCookieStore().get(request.getUri()); + if (!cookies.isEmpty()) { + RequestBuilder requestBuilder = request.toBuilder(); + for (Cookie cookie : cookies) { + requestBuilder.addCookieIfUnset(cookie); + } + request = requestBuilder.build(); + } + } catch (Exception e) { + handler.onThrowable(e); + return new ListenableFuture.CompletedFailure<>("Failed to set cookies of request", e); + } + } + + if (noRequestFilters) { + return execute(request, handler); + } else { + FilterContext fc = new FilterContext.FilterContextBuilder<>(handler, request).build(); + try { + fc = preProcessRequest(fc); + } catch (Exception e) { + handler.onThrowable(e); + return new ListenableFuture.CompletedFailure<>("preProcessRequest failed", e); + } + + return execute(fc.getRequest(), fc.getAsyncHandler()); + } + } + + @Override + public ListenableFuture executeRequest(RequestBuilder requestBuilder, AsyncHandler handler) { + return executeRequest(requestBuilder.build(), handler); + } + + @Override + public ListenableFuture executeRequest(Request request) { + return executeRequest(request, new AsyncCompletionHandlerBase()); + } + + @Override + public ListenableFuture executeRequest(RequestBuilder requestBuilder) { + return executeRequest(requestBuilder.build()); + } + + private ListenableFuture execute(Request request, final AsyncHandler asyncHandler) { + try { + return requestSender.sendRequest(request, asyncHandler, null); + } catch (Exception e) { + asyncHandler.onThrowable(e); + return new ListenableFuture.CompletedFailure<>(e); + } + } + + /** + * Configure and execute the associated {@link RequestFilter}. This class + * may decorate the {@link Request} and {@link AsyncHandler} + * + * @param fc {@link FilterContext} + * @return {@link FilterContext} + */ + private FilterContext preProcessRequest(FilterContext fc) throws FilterException { + for (RequestFilter asyncFilter : config.getRequestFilters()) { + fc = asyncFilter.filter(fc); + requireNonNull(fc, "filterContext"); + } + + Request request = fc.getRequest(); + if (fc.getAsyncHandler() instanceof ResumableAsyncHandler) { + request = ((ResumableAsyncHandler) fc.getAsyncHandler()).adjustRequestRange(request); + } + + if (request.getRangeOffset() != 0) { + RequestBuilder builder = request.toBuilder(); + builder.setHeader("Range", "bytes=" + request.getRangeOffset() + '-'); + request = builder.build(); + } + fc = new FilterContext.FilterContextBuilder<>(fc).request(request).build(); + return fc; + } + + public ChannelPool getChannelPool() { + return channelManager.getChannelPool(); + } + + public EventLoopGroup getEventLoopGroup() { + return channelManager.getEventLoopGroup(); + } + + @Override + public ClientStats getClientStats() { + return channelManager.getClientStats(); + } + + @Override + public void flushChannelPoolPartitions(Predicate predicate) { + getChannelPool().flushPartitions(predicate); + } + + protected BoundRequestBuilder requestBuilder(String method, String url) { + return new BoundRequestBuilder(this, method, config.isDisableUrlEncodingForBoundRequests()).setUrl(url).setSignatureCalculator(signatureCalculator); + } + + protected BoundRequestBuilder requestBuilder(Request prototype) { + return new BoundRequestBuilder(this, prototype).setSignatureCalculator(signatureCalculator); + } + + @Override + public AsyncHttpClientConfig getConfig() { + return config; + } +} diff --git a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java new file mode 100644 index 0000000000..1c7dbf37f8 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java @@ -0,0 +1,1523 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.handler.ssl.SslContext; +import io.netty.util.Timer; +import org.asynchttpclient.channel.ChannelPool; +import org.asynchttpclient.channel.DefaultKeepAliveStrategy; +import org.asynchttpclient.channel.KeepAliveStrategy; +import org.asynchttpclient.config.AsyncHttpClientConfigDefaults; +import org.asynchttpclient.cookie.CookieStore; +import org.asynchttpclient.cookie.ThreadSafeCookieStore; +import org.asynchttpclient.filter.IOExceptionFilter; +import org.asynchttpclient.filter.RequestFilter; +import org.asynchttpclient.filter.ResponseFilter; +import org.asynchttpclient.netty.channel.ConnectionSemaphoreFactory; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.proxy.ProxyServerSelector; +import org.asynchttpclient.util.ProxyUtils; +import org.jetbrains.annotations.Nullable; + +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadFactory; +import java.util.function.Consumer; + +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultAcquireFreeChannelTimeout; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultAggregateWebSocketFrameFragments; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultChunkedFileChunkSize; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultCompressionEnforced; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultConnectTimeout; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultConnectionPoolCleanerPeriod; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultConnectionTtl; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultDisableHttpsEndpointIdentificationAlgorithm; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultDisableUrlEncodingForBoundRequests; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultDisableZeroCopy; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultEnableAutomaticDecompression; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultEnableWebSocketCompression; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultEnabledCipherSuites; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultEnabledProtocols; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultExpiredCookieEvictionDelay; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultFilterInsecureCipherSuites; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultFollowRedirect; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHandshakeTimeout; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHashedWheelTimerSize; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHashedWheelTimerTickDuration; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHttpClientCodecInitialBufferSize; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHttpClientCodecMaxChunkSize; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHttpClientCodecMaxHeaderSize; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHttpClientCodecMaxInitialLineLength; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultIoThreadsCount; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultKeepAlive; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultKeepEncodingHeader; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultMaxConnections; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultMaxConnectionsPerHost; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultMaxRedirects; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultMaxRequestRetry; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultPooledConnectionIdleTimeout; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultReadTimeout; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultRequestTimeout; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultShutdownQuietPeriod; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultShutdownTimeout; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultSoKeepAlive; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultSoLinger; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultSoRcvBuf; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultSoReuseAddress; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultSoSndBuf; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultSslSessionCacheSize; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultSslSessionTimeout; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultStrict302Handling; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultTcpNoDelay; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultThreadPoolName; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseInsecureTrustManager; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseLaxCookieEncoder; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseNativeTransport; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseOnlyEpollNativeTransport; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseOpenSsl; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseProxyProperties; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUseProxySelector; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUserAgent; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultValidateResponseHeaders; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultWebSocketMaxBufferSize; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultWebSocketMaxFrameSize; + +/** + * Configuration class to use with a {@link AsyncHttpClient}. System property can be also used to configure this object default behavior by doing:
+ * -Dorg.asynchttpclient.nameOfTheProperty + * + * @see AsyncHttpClientConfig for documentation + */ +public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig { + + // http + private final boolean followRedirect; + private final int maxRedirects; + private final boolean strict302Handling; + private final boolean compressionEnforced; + + private final boolean enableAutomaticDecompression; + private final String userAgent; + private final @Nullable Realm realm; + private final int maxRequestRetry; + private final boolean disableUrlEncodingForBoundRequests; + private final boolean useLaxCookieEncoder; + private final boolean disableZeroCopy; + private final boolean keepEncodingHeader; + private final ProxyServerSelector proxyServerSelector; + private final boolean validateResponseHeaders; + private final boolean stripAuthorizationOnRedirect; + + // websockets + private final boolean aggregateWebSocketFrameFragments; + private final boolean enablewebSocketCompression; + private final int webSocketMaxBufferSize; + private final int webSocketMaxFrameSize; + + // timeouts + private final Duration connectTimeout; + private final Duration requestTimeout; + private final Duration readTimeout; + private final Duration shutdownQuietPeriod; + private final Duration shutdownTimeout; + + // keep-alive + private final boolean keepAlive; + private final Duration pooledConnectionIdleTimeout; + private final Duration connectionPoolCleanerPeriod; + private final Duration connectionTtl; + private final int maxConnections; + private final int maxConnectionsPerHost; + private final int acquireFreeChannelTimeout; + private final @Nullable ChannelPool channelPool; + private final @Nullable ConnectionSemaphoreFactory connectionSemaphoreFactory; + private final KeepAliveStrategy keepAliveStrategy; + + // ssl + private final boolean useOpenSsl; + private final boolean useInsecureTrustManager; + private final boolean disableHttpsEndpointIdentificationAlgorithm; + private final int handshakeTimeout; + private final @Nullable String[] enabledProtocols; + private final @Nullable String[] enabledCipherSuites; + private final boolean filterInsecureCipherSuites; + private final int sslSessionCacheSize; + private final int sslSessionTimeout; + private final @Nullable SslContext sslContext; + private final @Nullable SslEngineFactory sslEngineFactory; + + // filters + private final List requestFilters; + private final List responseFilters; + private final List ioExceptionFilters; + + // cookie store + private final CookieStore cookieStore; + private final int expiredCookieEvictionDelay; + + // internals + private final String threadPoolName; + private final int httpClientCodecMaxInitialLineLength; + private final int httpClientCodecMaxHeaderSize; + private final int httpClientCodecMaxChunkSize; + private final int httpClientCodecInitialBufferSize; + private final int chunkedFileChunkSize; + private final Map, Object> channelOptions; + private final @Nullable EventLoopGroup eventLoopGroup; + private final boolean useNativeTransport; + private final boolean useOnlyEpollNativeTransport; + private final @Nullable ByteBufAllocator allocator; + private final boolean tcpNoDelay; + private final boolean soReuseAddress; + private final boolean soKeepAlive; + private final int soLinger; + private final int soSndBuf; + private final int soRcvBuf; + private final @Nullable Timer nettyTimer; + private final @Nullable ThreadFactory threadFactory; + private final @Nullable Consumer httpAdditionalChannelInitializer; + private final @Nullable Consumer wsAdditionalChannelInitializer; + private final ResponseBodyPartFactory responseBodyPartFactory; + private final int ioThreadsCount; + private final long hashedWheelTimerTickDuration; + private final int hashedWheelTimerSize; + + private DefaultAsyncHttpClientConfig(// http + boolean followRedirect, + int maxRedirects, + boolean strict302Handling, + boolean compressionEnforced, + boolean enableAutomaticDecompression, + String userAgent, + @Nullable Realm realm, + int maxRequestRetry, + boolean disableUrlEncodingForBoundRequests, + boolean useLaxCookieEncoder, + boolean disableZeroCopy, + boolean keepEncodingHeader, + ProxyServerSelector proxyServerSelector, + boolean validateResponseHeaders, + boolean aggregateWebSocketFrameFragments, + boolean enablewebSocketCompression, + boolean stripAuthorizationOnRedirect, + + // timeouts + Duration connectTimeout, + Duration requestTimeout, + Duration readTimeout, + Duration shutdownQuietPeriod, + Duration shutdownTimeout, + + // keep-alive + boolean keepAlive, + Duration pooledConnectionIdleTimeout, + Duration connectionPoolCleanerPeriod, + Duration connectionTtl, + int maxConnections, + int maxConnectionsPerHost, + int acquireFreeChannelTimeout, + @Nullable ChannelPool channelPool, + @Nullable ConnectionSemaphoreFactory connectionSemaphoreFactory, + KeepAliveStrategy keepAliveStrategy, + + // ssl + boolean useOpenSsl, + boolean useInsecureTrustManager, + boolean disableHttpsEndpointIdentificationAlgorithm, + int handshakeTimeout, + @Nullable String[] enabledProtocols, + @Nullable String[] enabledCipherSuites, + boolean filterInsecureCipherSuites, + int sslSessionCacheSize, + int sslSessionTimeout, + @Nullable SslContext sslContext, + @Nullable SslEngineFactory sslEngineFactory, + + // filters + List requestFilters, + List responseFilters, + List ioExceptionFilters, + + // cookie store + CookieStore cookieStore, + int expiredCookieEvictionDelay, + + // tuning + boolean tcpNoDelay, + boolean soReuseAddress, + boolean soKeepAlive, + int soLinger, + int soSndBuf, + int soRcvBuf, + + // internals + String threadPoolName, + int httpClientCodecMaxInitialLineLength, + int httpClientCodecMaxHeaderSize, + int httpClientCodecMaxChunkSize, + int httpClientCodecInitialBufferSize, + int chunkedFileChunkSize, + int webSocketMaxBufferSize, + int webSocketMaxFrameSize, + Map, Object> channelOptions, + @Nullable EventLoopGroup eventLoopGroup, + boolean useNativeTransport, + boolean useOnlyEpollNativeTransport, + @Nullable ByteBufAllocator allocator, + @Nullable Timer nettyTimer, + @Nullable ThreadFactory threadFactory, + @Nullable Consumer httpAdditionalChannelInitializer, + @Nullable Consumer wsAdditionalChannelInitializer, + ResponseBodyPartFactory responseBodyPartFactory, + int ioThreadsCount, + long hashedWheelTimerTickDuration, + int hashedWheelTimerSize) { + + // http + this.followRedirect = followRedirect; + this.maxRedirects = maxRedirects; + this.strict302Handling = strict302Handling; + this.compressionEnforced = compressionEnforced; + this.enableAutomaticDecompression = enableAutomaticDecompression; + this.userAgent = userAgent; + this.realm = realm; + this.maxRequestRetry = maxRequestRetry; + this.disableUrlEncodingForBoundRequests = disableUrlEncodingForBoundRequests; + this.useLaxCookieEncoder = useLaxCookieEncoder; + this.disableZeroCopy = disableZeroCopy; + this.keepEncodingHeader = keepEncodingHeader; + this.proxyServerSelector = proxyServerSelector; + this.validateResponseHeaders = validateResponseHeaders; + this.stripAuthorizationOnRedirect = stripAuthorizationOnRedirect; + + // websocket + this.aggregateWebSocketFrameFragments = aggregateWebSocketFrameFragments; + this.enablewebSocketCompression = enablewebSocketCompression; + this.webSocketMaxBufferSize = webSocketMaxBufferSize; + this.webSocketMaxFrameSize = webSocketMaxFrameSize; + + // timeouts + this.connectTimeout = connectTimeout; + this.requestTimeout = requestTimeout; + this.readTimeout = readTimeout; + this.shutdownQuietPeriod = shutdownQuietPeriod; + this.shutdownTimeout = shutdownTimeout; + + // keep-alive + this.keepAlive = keepAlive; + this.pooledConnectionIdleTimeout = pooledConnectionIdleTimeout; + this.connectionPoolCleanerPeriod = connectionPoolCleanerPeriod; + this.connectionTtl = connectionTtl; + this.maxConnections = maxConnections; + this.maxConnectionsPerHost = maxConnectionsPerHost; + this.acquireFreeChannelTimeout = acquireFreeChannelTimeout; + this.channelPool = channelPool; + this.connectionSemaphoreFactory = connectionSemaphoreFactory; + this.keepAliveStrategy = keepAliveStrategy; + + // ssl + this.useOpenSsl = useOpenSsl; + this.useInsecureTrustManager = useInsecureTrustManager; + this.disableHttpsEndpointIdentificationAlgorithm = disableHttpsEndpointIdentificationAlgorithm; + this.handshakeTimeout = handshakeTimeout; + this.enabledProtocols = enabledProtocols; + this.enabledCipherSuites = enabledCipherSuites; + this.filterInsecureCipherSuites = filterInsecureCipherSuites; + this.sslSessionCacheSize = sslSessionCacheSize; + this.sslSessionTimeout = sslSessionTimeout; + this.sslContext = sslContext; + this.sslEngineFactory = sslEngineFactory; + + // filters + this.requestFilters = requestFilters; + this.responseFilters = responseFilters; + this.ioExceptionFilters = ioExceptionFilters; + + // cookie store + this.cookieStore = cookieStore; + this.expiredCookieEvictionDelay = expiredCookieEvictionDelay; + + // tuning + this.tcpNoDelay = tcpNoDelay; + this.soReuseAddress = soReuseAddress; + this.soKeepAlive = soKeepAlive; + this.soLinger = soLinger; + this.soSndBuf = soSndBuf; + this.soRcvBuf = soRcvBuf; + + // internals + this.threadPoolName = threadPoolName; + this.httpClientCodecMaxInitialLineLength = httpClientCodecMaxInitialLineLength; + this.httpClientCodecMaxHeaderSize = httpClientCodecMaxHeaderSize; + this.httpClientCodecMaxChunkSize = httpClientCodecMaxChunkSize; + this.httpClientCodecInitialBufferSize = httpClientCodecInitialBufferSize; + this.chunkedFileChunkSize = chunkedFileChunkSize; + this.channelOptions = channelOptions; + this.eventLoopGroup = eventLoopGroup; + this.useNativeTransport = useNativeTransport; + this.useOnlyEpollNativeTransport = useOnlyEpollNativeTransport; + + if (useOnlyEpollNativeTransport && !useNativeTransport) { + throw new IllegalArgumentException("Native Transport must be enabled to use Epoll Native Transport only"); + } + + this.allocator = allocator; + this.nettyTimer = nettyTimer; + this.threadFactory = threadFactory; + this.httpAdditionalChannelInitializer = httpAdditionalChannelInitializer; + this.wsAdditionalChannelInitializer = wsAdditionalChannelInitializer; + this.responseBodyPartFactory = responseBodyPartFactory; + this.ioThreadsCount = ioThreadsCount; + this.hashedWheelTimerTickDuration = hashedWheelTimerTickDuration; + this.hashedWheelTimerSize = hashedWheelTimerSize; + } + + @Override + public String getAhcVersion() { + return AsyncHttpClientConfigDefaults.AHC_VERSION; + } + + // http + @Override + public boolean isFollowRedirect() { + return followRedirect; + } + + @Override + public int getMaxRedirects() { + return maxRedirects; + } + + @Override + public boolean isStrict302Handling() { + return strict302Handling; + } + + @Override + public boolean isCompressionEnforced() { + return compressionEnforced; + } + + @Override + public boolean isEnableAutomaticDecompression() { + return enableAutomaticDecompression; + } + + @Override + public String getUserAgent() { + return userAgent; + } + + @Override + public @Nullable Realm getRealm() { + return realm; + } + + @Override + public int getMaxRequestRetry() { + return maxRequestRetry; + } + + @Override + public boolean isDisableUrlEncodingForBoundRequests() { + return disableUrlEncodingForBoundRequests; + } + + @Override + public boolean isUseLaxCookieEncoder() { + return useLaxCookieEncoder; + } + + @Override + public boolean isDisableZeroCopy() { + return disableZeroCopy; + } + + @Override + public boolean isKeepEncodingHeader() { + return keepEncodingHeader; + } + + @Override + public ProxyServerSelector getProxyServerSelector() { + return proxyServerSelector; + } + + // websocket + @Override + public boolean isAggregateWebSocketFrameFragments() { + return aggregateWebSocketFrameFragments; + } + + @Override + public boolean isEnableWebSocketCompression() { + return enablewebSocketCompression; + } + + @Override + public int getWebSocketMaxBufferSize() { + return webSocketMaxBufferSize; + } + + @Override + public int getWebSocketMaxFrameSize() { + return webSocketMaxFrameSize; + } + + // timeouts + @Override + public Duration getConnectTimeout() { + return connectTimeout; + } + + @Override + public Duration getRequestTimeout() { + return requestTimeout; + } + + @Override + public Duration getReadTimeout() { + return readTimeout; + } + + @Override + public Duration getShutdownQuietPeriod() { + return shutdownQuietPeriod; + } + + @Override + public Duration getShutdownTimeout() { + return shutdownTimeout; + } + + // keep-alive + @Override + public boolean isKeepAlive() { + return keepAlive; + } + + @Override + public Duration getPooledConnectionIdleTimeout() { + return pooledConnectionIdleTimeout; + } + + @Override + public Duration getConnectionPoolCleanerPeriod() { + return connectionPoolCleanerPeriod; + } + + @Override + public Duration getConnectionTtl() { + return connectionTtl; + } + + @Override + public int getMaxConnections() { + return maxConnections; + } + + @Override + public int getMaxConnectionsPerHost() { + return maxConnectionsPerHost; + } + + @Override + public int getAcquireFreeChannelTimeout() { + return acquireFreeChannelTimeout; + } + + @Override + public @Nullable ChannelPool getChannelPool() { + return channelPool; + } + + @Override + public @Nullable ConnectionSemaphoreFactory getConnectionSemaphoreFactory() { + return connectionSemaphoreFactory; + } + + @Override + public KeepAliveStrategy getKeepAliveStrategy() { + return keepAliveStrategy; + } + + @Override + public boolean isValidateResponseHeaders() { + return validateResponseHeaders; + } + + @Override + public boolean isStripAuthorizationOnRedirect() { + return stripAuthorizationOnRedirect; + } + + // ssl + @Override + public boolean isUseOpenSsl() { + return useOpenSsl; + } + + @Override + public boolean isUseInsecureTrustManager() { + return useInsecureTrustManager; + } + + @Override + public boolean isDisableHttpsEndpointIdentificationAlgorithm() { + return disableHttpsEndpointIdentificationAlgorithm; + } + + @Override + public int getHandshakeTimeout() { + return handshakeTimeout; + } + + @Override + public @Nullable String[] getEnabledProtocols() { + return enabledProtocols; + } + + @Override + public @Nullable String[] getEnabledCipherSuites() { + return enabledCipherSuites; + } + + @Override + public boolean isFilterInsecureCipherSuites() { + return filterInsecureCipherSuites; + } + + @Override + public int getSslSessionCacheSize() { + return sslSessionCacheSize; + } + + @Override + public int getSslSessionTimeout() { + return sslSessionTimeout; + } + + @Override + public @Nullable SslContext getSslContext() { + return sslContext; + } + + @Override + public @Nullable SslEngineFactory getSslEngineFactory() { + return sslEngineFactory; + } + + // filters + @Override + public List getRequestFilters() { + return requestFilters; + } + + @Override + public List getResponseFilters() { + return responseFilters; + } + + @Override + public List getIoExceptionFilters() { + return ioExceptionFilters; + } + + // cookie store + @Override + public CookieStore getCookieStore() { + return cookieStore; + } + + @Override + public int expiredCookieEvictionDelay() { + return expiredCookieEvictionDelay; + } + + // tuning + @Override + public boolean isTcpNoDelay() { + return tcpNoDelay; + } + + @Override + public boolean isSoReuseAddress() { + return soReuseAddress; + } + + @Override + public boolean isSoKeepAlive() { + return soKeepAlive; + } + + @Override + public int getSoLinger() { + return soLinger; + } + + @Override + public int getSoSndBuf() { + return soSndBuf; + } + + @Override + public int getSoRcvBuf() { + return soRcvBuf; + } + + // internals + @Override + public String getThreadPoolName() { + return threadPoolName; + } + + @Override + public int getHttpClientCodecMaxInitialLineLength() { + return httpClientCodecMaxInitialLineLength; + } + + @Override + public int getHttpClientCodecMaxHeaderSize() { + return httpClientCodecMaxHeaderSize; + } + + @Override + public int getHttpClientCodecMaxChunkSize() { + return httpClientCodecMaxChunkSize; + } + + @Override + public int getHttpClientCodecInitialBufferSize() { + return httpClientCodecInitialBufferSize; + } + + @Override + public int getChunkedFileChunkSize() { + return chunkedFileChunkSize; + } + + @Override + public Map, Object> getChannelOptions() { + return channelOptions; + } + + @Override + public @Nullable EventLoopGroup getEventLoopGroup() { + return eventLoopGroup; + } + + @Override + public boolean isUseNativeTransport() { + return useNativeTransport; + } + + @Override + public boolean isUseOnlyEpollNativeTransport() { + return useOnlyEpollNativeTransport; + } + + @Override + public @Nullable ByteBufAllocator getAllocator() { + return allocator; + } + + @Override + public @Nullable Timer getNettyTimer() { + return nettyTimer; + } + + @Override + public long getHashedWheelTimerTickDuration() { + return hashedWheelTimerTickDuration; + } + + @Override + public int getHashedWheelTimerSize() { + return hashedWheelTimerSize; + } + + @Override + public @Nullable ThreadFactory getThreadFactory() { + return threadFactory; + } + + @Override + public @Nullable Consumer getHttpAdditionalChannelInitializer() { + return httpAdditionalChannelInitializer; + } + + @Override + public @Nullable Consumer getWsAdditionalChannelInitializer() { + return wsAdditionalChannelInitializer; + } + + @Override + public ResponseBodyPartFactory getResponseBodyPartFactory() { + return responseBodyPartFactory; + } + + @Override + public int getIoThreadsCount() { + return ioThreadsCount; + } + + /** + * Builder for an {@link AsyncHttpClient} + */ + public static class Builder { + + // filters + private final List requestFilters = new LinkedList<>(); + private final List responseFilters = new LinkedList<>(); + private final List ioExceptionFilters = new LinkedList<>(); + // http + private boolean followRedirect = defaultFollowRedirect(); + private int maxRedirects = defaultMaxRedirects(); + private boolean strict302Handling = defaultStrict302Handling(); + private boolean compressionEnforced = defaultCompressionEnforced(); + private boolean enableAutomaticDecompression = defaultEnableAutomaticDecompression(); + private String userAgent = defaultUserAgent(); + private @Nullable Realm realm; + private int maxRequestRetry = defaultMaxRequestRetry(); + private boolean disableUrlEncodingForBoundRequests = defaultDisableUrlEncodingForBoundRequests(); + private boolean useLaxCookieEncoder = defaultUseLaxCookieEncoder(); + private boolean disableZeroCopy = defaultDisableZeroCopy(); + private boolean keepEncodingHeader = defaultKeepEncodingHeader(); + private @Nullable ProxyServerSelector proxyServerSelector; + private boolean useProxySelector = defaultUseProxySelector(); + private boolean useProxyProperties = defaultUseProxyProperties(); + private boolean validateResponseHeaders = defaultValidateResponseHeaders(); + private boolean stripAuthorizationOnRedirect = false; // default value + + // websocket + private boolean aggregateWebSocketFrameFragments = defaultAggregateWebSocketFrameFragments(); + private boolean enablewebSocketCompression = defaultEnableWebSocketCompression(); + private int webSocketMaxBufferSize = defaultWebSocketMaxBufferSize(); + private int webSocketMaxFrameSize = defaultWebSocketMaxFrameSize(); + + // timeouts + private Duration connectTimeout = defaultConnectTimeout(); + private Duration requestTimeout = defaultRequestTimeout(); + private Duration readTimeout = defaultReadTimeout(); + private Duration shutdownQuietPeriod = defaultShutdownQuietPeriod(); + private Duration shutdownTimeout = defaultShutdownTimeout(); + + // keep-alive + private boolean keepAlive = defaultKeepAlive(); + private Duration pooledConnectionIdleTimeout = defaultPooledConnectionIdleTimeout(); + private Duration connectionPoolCleanerPeriod = defaultConnectionPoolCleanerPeriod(); + private Duration connectionTtl = defaultConnectionTtl(); + private int maxConnections = defaultMaxConnections(); + private int maxConnectionsPerHost = defaultMaxConnectionsPerHost(); + private int acquireFreeChannelTimeout = defaultAcquireFreeChannelTimeout(); + private @Nullable ChannelPool channelPool; + private @Nullable ConnectionSemaphoreFactory connectionSemaphoreFactory; + private KeepAliveStrategy keepAliveStrategy = new DefaultKeepAliveStrategy(); + + // ssl + private boolean useOpenSsl = defaultUseOpenSsl(); + private boolean useInsecureTrustManager = defaultUseInsecureTrustManager(); + private boolean disableHttpsEndpointIdentificationAlgorithm = defaultDisableHttpsEndpointIdentificationAlgorithm(); + private int handshakeTimeout = defaultHandshakeTimeout(); + private @Nullable String[] enabledProtocols = defaultEnabledProtocols(); + private @Nullable String[] enabledCipherSuites = defaultEnabledCipherSuites(); + private boolean filterInsecureCipherSuites = defaultFilterInsecureCipherSuites(); + private int sslSessionCacheSize = defaultSslSessionCacheSize(); + private int sslSessionTimeout = defaultSslSessionTimeout(); + private @Nullable SslContext sslContext; + private @Nullable SslEngineFactory sslEngineFactory; + + // cookie store + private CookieStore cookieStore = new ThreadSafeCookieStore(); + private int expiredCookieEvictionDelay = defaultExpiredCookieEvictionDelay(); + + // tuning + private boolean tcpNoDelay = defaultTcpNoDelay(); + private boolean soReuseAddress = defaultSoReuseAddress(); + private boolean soKeepAlive = defaultSoKeepAlive(); + private int soLinger = defaultSoLinger(); + private int soSndBuf = defaultSoSndBuf(); + private int soRcvBuf = defaultSoRcvBuf(); + + // internals + private String threadPoolName = defaultThreadPoolName(); + private int httpClientCodecMaxInitialLineLength = defaultHttpClientCodecMaxInitialLineLength(); + private int httpClientCodecMaxHeaderSize = defaultHttpClientCodecMaxHeaderSize(); + private int httpClientCodecMaxChunkSize = defaultHttpClientCodecMaxChunkSize(); + private int httpClientCodecInitialBufferSize = defaultHttpClientCodecInitialBufferSize(); + private int chunkedFileChunkSize = defaultChunkedFileChunkSize(); + private boolean useNativeTransport = defaultUseNativeTransport(); + private boolean useOnlyEpollNativeTransport = defaultUseOnlyEpollNativeTransport(); + private @Nullable ByteBufAllocator allocator; + private final Map, Object> channelOptions = new HashMap<>(); + private @Nullable EventLoopGroup eventLoopGroup; + private @Nullable Timer nettyTimer; + private @Nullable ThreadFactory threadFactory; + private @Nullable Consumer httpAdditionalChannelInitializer; + private @Nullable Consumer wsAdditionalChannelInitializer; + private ResponseBodyPartFactory responseBodyPartFactory = ResponseBodyPartFactory.EAGER; + private int ioThreadsCount = defaultIoThreadsCount(); + private long hashedWheelTickDuration = defaultHashedWheelTimerTickDuration(); + private int hashedWheelSize = defaultHashedWheelTimerSize(); + + public Builder() { + } + + public Builder(AsyncHttpClientConfig config) { + // http + followRedirect = config.isFollowRedirect(); + maxRedirects = config.getMaxRedirects(); + strict302Handling = config.isStrict302Handling(); + compressionEnforced = config.isCompressionEnforced(); + enableAutomaticDecompression = config.isEnableAutomaticDecompression(); + userAgent = config.getUserAgent(); + realm = config.getRealm(); + maxRequestRetry = config.getMaxRequestRetry(); + disableUrlEncodingForBoundRequests = config.isDisableUrlEncodingForBoundRequests(); + useLaxCookieEncoder = config.isUseLaxCookieEncoder(); + disableZeroCopy = config.isDisableZeroCopy(); + keepEncodingHeader = config.isKeepEncodingHeader(); + proxyServerSelector = config.getProxyServerSelector(); + validateResponseHeaders = config.isValidateResponseHeaders(); + stripAuthorizationOnRedirect = config.isStripAuthorizationOnRedirect(); + + // websocket + aggregateWebSocketFrameFragments = config.isAggregateWebSocketFrameFragments(); + enablewebSocketCompression = config.isEnableWebSocketCompression(); + webSocketMaxBufferSize = config.getWebSocketMaxBufferSize(); + webSocketMaxFrameSize = config.getWebSocketMaxFrameSize(); + + // timeouts + connectTimeout = config.getConnectTimeout(); + requestTimeout = config.getRequestTimeout(); + readTimeout = config.getReadTimeout(); + shutdownQuietPeriod = config.getShutdownQuietPeriod(); + shutdownTimeout = config.getShutdownTimeout(); + + // 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(); + filterInsecureCipherSuites = config.isFilterInsecureCipherSuites(); + sslSessionCacheSize = config.getSslSessionCacheSize(); + sslSessionTimeout = config.getSslSessionTimeout(); + sslContext = config.getSslContext(); + sslEngineFactory = config.getSslEngineFactory(); + + // filters + requestFilters.addAll(config.getRequestFilters()); + responseFilters.addAll(config.getResponseFilters()); + ioExceptionFilters.addAll(config.getIoExceptionFilters()); + + // cookie store + cookieStore = config.getCookieStore(); + expiredCookieEvictionDelay = config.expiredCookieEvictionDelay(); + + // tuning + tcpNoDelay = config.isTcpNoDelay(); + soReuseAddress = config.isSoReuseAddress(); + soKeepAlive = config.isSoKeepAlive(); + soLinger = config.getSoLinger(); + soSndBuf = config.getSoSndBuf(); + soRcvBuf = config.getSoRcvBuf(); + + // internals + threadPoolName = config.getThreadPoolName(); + httpClientCodecMaxInitialLineLength = config.getHttpClientCodecMaxInitialLineLength(); + httpClientCodecMaxHeaderSize = config.getHttpClientCodecMaxHeaderSize(); + httpClientCodecMaxChunkSize = config.getHttpClientCodecMaxChunkSize(); + httpClientCodecInitialBufferSize = config.getHttpClientCodecInitialBufferSize(); + chunkedFileChunkSize = config.getChunkedFileChunkSize(); + channelOptions.putAll(config.getChannelOptions()); + eventLoopGroup = config.getEventLoopGroup(); + useNativeTransport = config.isUseNativeTransport(); + useOnlyEpollNativeTransport = config.isUseOnlyEpollNativeTransport(); + + allocator = config.getAllocator(); + nettyTimer = config.getNettyTimer(); + threadFactory = config.getThreadFactory(); + httpAdditionalChannelInitializer = config.getHttpAdditionalChannelInitializer(); + wsAdditionalChannelInitializer = config.getWsAdditionalChannelInitializer(); + responseBodyPartFactory = config.getResponseBodyPartFactory(); + ioThreadsCount = config.getIoThreadsCount(); + hashedWheelTickDuration = config.getHashedWheelTimerTickDuration(); + hashedWheelSize = config.getHashedWheelTimerSize(); + } + + // http + public Builder setFollowRedirect(boolean followRedirect) { + this.followRedirect = followRedirect; + return this; + } + + public Builder setMaxRedirects(int maxRedirects) { + this.maxRedirects = maxRedirects; + return this; + } + + public Builder setStrict302Handling(final boolean strict302Handling) { + this.strict302Handling = strict302Handling; + return this; + } + + /** + * 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) + */ + public Builder setCompressionEnforced(boolean compressionEnforced) { + this.compressionEnforced = compressionEnforced; + return this; + } + + /* + * If true (default), AHC will add a Netty HttpContentDecompressor, so compressed + * content will automatically get decompressed. + * + * If set to false, response will be delivered as is received. Decompression must + * be done by calling code. + */ + public Builder setEnableAutomaticDecompression(boolean enable) { + enableAutomaticDecompression = enable; + return this; + } + + public Builder setUserAgent(String userAgent) { + this.userAgent = userAgent; + return this; + } + + public Builder setRealm(Realm realm) { + this.realm = realm; + return this; + } + + public Builder setRealm(Realm.Builder realmBuilder) { + realm = realmBuilder.build(); + return this; + } + + public Builder setMaxRequestRetry(int maxRequestRetry) { + this.maxRequestRetry = maxRequestRetry; + return this; + } + + public Builder setDisableUrlEncodingForBoundRequests(boolean disableUrlEncodingForBoundRequests) { + this.disableUrlEncodingForBoundRequests = disableUrlEncodingForBoundRequests; + return this; + } + + public Builder setUseLaxCookieEncoder(boolean useLaxCookieEncoder) { + this.useLaxCookieEncoder = useLaxCookieEncoder; + return this; + } + + public Builder setDisableZeroCopy(boolean disableZeroCopy) { + this.disableZeroCopy = disableZeroCopy; + return this; + } + + public Builder setKeepEncodingHeader(boolean keepEncodingHeader) { + this.keepEncodingHeader = keepEncodingHeader; + return this; + } + + public Builder setProxyServerSelector(ProxyServerSelector proxyServerSelector) { + this.proxyServerSelector = proxyServerSelector; + return this; + } + + public Builder setValidateResponseHeaders(boolean validateResponseHeaders) { + this.validateResponseHeaders = validateResponseHeaders; + return this; + } + + public Builder setProxyServer(ProxyServer proxyServer) { + proxyServerSelector = uri -> proxyServer; + return this; + } + + public Builder setProxyServer(ProxyServer.Builder proxyServerBuilder) { + return setProxyServer(proxyServerBuilder.build()); + } + + public Builder setUseProxySelector(boolean useProxySelector) { + this.useProxySelector = useProxySelector; + return this; + } + + public Builder setUseProxyProperties(boolean useProxyProperties) { + this.useProxyProperties = useProxyProperties; + return this; + } + + public Builder setStripAuthorizationOnRedirect(boolean value) { + stripAuthorizationOnRedirect = value; + return this; + } + + // websocket + public Builder setAggregateWebSocketFrameFragments(boolean aggregateWebSocketFrameFragments) { + this.aggregateWebSocketFrameFragments = aggregateWebSocketFrameFragments; + return this; + } + + public Builder setEnablewebSocketCompression(boolean enablewebSocketCompression) { + this.enablewebSocketCompression = enablewebSocketCompression; + return this; + } + + public Builder setWebSocketMaxBufferSize(int webSocketMaxBufferSize) { + this.webSocketMaxBufferSize = webSocketMaxBufferSize; + return this; + } + + public Builder setWebSocketMaxFrameSize(int webSocketMaxFrameSize) { + this.webSocketMaxFrameSize = webSocketMaxFrameSize; + return this; + } + + // timeouts + public Builder setConnectTimeout(Duration connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } + + public Builder setRequestTimeout(Duration requestTimeout) { + this.requestTimeout = requestTimeout; + return this; + } + + public Builder setReadTimeout(Duration readTimeout) { + this.readTimeout = readTimeout; + return this; + } + + public Builder setShutdownQuietPeriod(Duration shutdownQuietPeriod) { + this.shutdownQuietPeriod = shutdownQuietPeriod; + return this; + } + + public Builder setShutdownTimeout(Duration shutdownTimeout) { + this.shutdownTimeout = shutdownTimeout; + return this; + } + + // keep-alive + public Builder setKeepAlive(boolean keepAlive) { + this.keepAlive = keepAlive; + return this; + } + + public Builder setPooledConnectionIdleTimeout(Duration pooledConnectionIdleTimeout) { + this.pooledConnectionIdleTimeout = pooledConnectionIdleTimeout; + return this; + } + + public Builder setConnectionPoolCleanerPeriod(Duration connectionPoolCleanerPeriod) { + this.connectionPoolCleanerPeriod = connectionPoolCleanerPeriod; + return this; + } + + public Builder setConnectionTtl(Duration connectionTtl) { + this.connectionTtl = connectionTtl; + return this; + } + + public Builder setMaxConnections(int maxConnections) { + this.maxConnections = maxConnections; + return this; + } + + public Builder setMaxConnectionsPerHost(int maxConnectionsPerHost) { + this.maxConnectionsPerHost = maxConnectionsPerHost; + return this; + } + + /** + * Sets the maximum duration in milliseconds to acquire a free channel to send a request + * + * @param acquireFreeChannelTimeout maximum duration in milliseconds to acquire a free channel to send a request + * @return the same builder instance + */ + public Builder setAcquireFreeChannelTimeout(int acquireFreeChannelTimeout) { + this.acquireFreeChannelTimeout = acquireFreeChannelTimeout; + return this; + } + + public Builder setChannelPool(ChannelPool channelPool) { + this.channelPool = channelPool; + return this; + } + + public Builder setConnectionSemaphoreFactory(ConnectionSemaphoreFactory connectionSemaphoreFactory) { + this.connectionSemaphoreFactory = connectionSemaphoreFactory; + return this; + } + + public Builder setKeepAliveStrategy(KeepAliveStrategy keepAliveStrategy) { + this.keepAliveStrategy = keepAliveStrategy; + return this; + } + + // ssl + public Builder setUseOpenSsl(boolean useOpenSsl) { + this.useOpenSsl = useOpenSsl; + return this; + } + + public Builder setUseInsecureTrustManager(boolean useInsecureTrustManager) { + this.useInsecureTrustManager = useInsecureTrustManager; + return this; + } + + public Builder setDisableHttpsEndpointIdentificationAlgorithm(boolean disableHttpsEndpointIdentificationAlgorithm) { + this.disableHttpsEndpointIdentificationAlgorithm = disableHttpsEndpointIdentificationAlgorithm; + return this; + } + + public Builder setHandshakeTimeout(int handshakeTimeout) { + this.handshakeTimeout = handshakeTimeout; + return this; + } + + public Builder setEnabledProtocols(String[] enabledProtocols) { + this.enabledProtocols = enabledProtocols; + return this; + } + + public Builder setEnabledCipherSuites(String[] enabledCipherSuites) { + this.enabledCipherSuites = enabledCipherSuites; + return this; + } + + public Builder setFilterInsecureCipherSuites(boolean filterInsecureCipherSuites) { + this.filterInsecureCipherSuites = filterInsecureCipherSuites; + return this; + } + + public Builder setSslSessionCacheSize(Integer sslSessionCacheSize) { + this.sslSessionCacheSize = sslSessionCacheSize; + return this; + } + + public Builder setSslSessionTimeout(Integer sslSessionTimeout) { + this.sslSessionTimeout = sslSessionTimeout; + return this; + } + + public Builder setSslContext(final SslContext sslContext) { + this.sslContext = sslContext; + return this; + } + + public Builder setSslEngineFactory(SslEngineFactory sslEngineFactory) { + this.sslEngineFactory = sslEngineFactory; + return this; + } + + // filters + public Builder addRequestFilter(RequestFilter requestFilter) { + requestFilters.add(requestFilter); + return this; + } + + public Builder removeRequestFilter(RequestFilter requestFilter) { + requestFilters.remove(requestFilter); + return this; + } + + public Builder addResponseFilter(ResponseFilter responseFilter) { + responseFilters.add(responseFilter); + return this; + } + + public Builder removeResponseFilter(ResponseFilter responseFilter) { + responseFilters.remove(responseFilter); + return this; + } + + public Builder addIOExceptionFilter(IOExceptionFilter ioExceptionFilter) { + ioExceptionFilters.add(ioExceptionFilter); + return this; + } + + public Builder removeIOExceptionFilter(IOExceptionFilter ioExceptionFilter) { + ioExceptionFilters.remove(ioExceptionFilter); + return this; + } + + // cookie store + public Builder setCookieStore(CookieStore cookieStore) { + this.cookieStore = cookieStore; + return this; + } + + public Builder setExpiredCookieEvictionDelay(int expiredCookieEvictionDelay) { + this.expiredCookieEvictionDelay = expiredCookieEvictionDelay; + return this; + } + + // tuning + public Builder setTcpNoDelay(boolean tcpNoDelay) { + this.tcpNoDelay = tcpNoDelay; + return this; + } + + public Builder setSoReuseAddress(boolean soReuseAddress) { + this.soReuseAddress = soReuseAddress; + return this; + } + + public Builder setSoKeepAlive(boolean soKeepAlive) { + this.soKeepAlive = soKeepAlive; + return this; + } + + public Builder setSoLinger(int soLinger) { + this.soLinger = soLinger; + return this; + } + + public Builder setSoSndBuf(int soSndBuf) { + this.soSndBuf = soSndBuf; + return this; + } + + public Builder setSoRcvBuf(int soRcvBuf) { + this.soRcvBuf = soRcvBuf; + return this; + } + + // internals + public Builder setThreadPoolName(String threadPoolName) { + this.threadPoolName = threadPoolName; + return this; + } + + public Builder setHttpClientCodecMaxInitialLineLength(int httpClientCodecMaxInitialLineLength) { + this.httpClientCodecMaxInitialLineLength = httpClientCodecMaxInitialLineLength; + return this; + } + + public Builder setHttpClientCodecMaxHeaderSize(int httpClientCodecMaxHeaderSize) { + this.httpClientCodecMaxHeaderSize = httpClientCodecMaxHeaderSize; + return this; + } + + public Builder setHttpClientCodecMaxChunkSize(int httpClientCodecMaxChunkSize) { + this.httpClientCodecMaxChunkSize = httpClientCodecMaxChunkSize; + return this; + } + + public Builder setHttpClientCodecInitialBufferSize(int httpClientCodecInitialBufferSize) { + this.httpClientCodecInitialBufferSize = httpClientCodecInitialBufferSize; + return this; + } + + public Builder setChunkedFileChunkSize(int chunkedFileChunkSize) { + this.chunkedFileChunkSize = chunkedFileChunkSize; + return this; + } + + public Builder setHashedWheelTickDuration(long hashedWheelTickDuration) { + this.hashedWheelTickDuration = hashedWheelTickDuration; + return this; + } + + public Builder setHashedWheelSize(int hashedWheelSize) { + this.hashedWheelSize = hashedWheelSize; + return this; + } + + @SuppressWarnings("unchecked") + public Builder addChannelOption(ChannelOption name, T value) { + channelOptions.put((ChannelOption) name, value); + return this; + } + + public Builder setEventLoopGroup(EventLoopGroup eventLoopGroup) { + this.eventLoopGroup = eventLoopGroup; + return this; + } + + public Builder setUseNativeTransport(boolean useNativeTransport) { + this.useNativeTransport = useNativeTransport; + return this; + } + + public Builder setUseOnlyEpollNativeTransport(boolean useOnlyEpollNativeTransport) { + this.useOnlyEpollNativeTransport = useOnlyEpollNativeTransport; + return this; + } + + public Builder setAllocator(ByteBufAllocator allocator) { + this.allocator = allocator; + return this; + } + + public Builder setNettyTimer(Timer nettyTimer) { + this.nettyTimer = nettyTimer; + return this; + } + + public Builder setThreadFactory(ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + return this; + } + + public Builder setHttpAdditionalChannelInitializer(Consumer httpAdditionalChannelInitializer) { + this.httpAdditionalChannelInitializer = httpAdditionalChannelInitializer; + return this; + } + + public Builder setWsAdditionalChannelInitializer(Consumer wsAdditionalChannelInitializer) { + this.wsAdditionalChannelInitializer = wsAdditionalChannelInitializer; + return this; + } + + public Builder setResponseBodyPartFactory(ResponseBodyPartFactory responseBodyPartFactory) { + this.responseBodyPartFactory = responseBodyPartFactory; + return this; + } + + public Builder setIoThreadsCount(int ioThreadsCount) { + this.ioThreadsCount = ioThreadsCount; + return this; + } + + private ProxyServerSelector resolveProxyServerSelector() { + if (proxyServerSelector != null) { + return proxyServerSelector; + } + + if (useProxySelector) { + return ProxyUtils.getJdkDefaultProxyServerSelector(); + } + + if (useProxyProperties) { + return ProxyUtils.createProxyServerSelector(System.getProperties()); + } + + return ProxyServerSelector.NO_PROXY_SELECTOR; + } + + public DefaultAsyncHttpClientConfig build() { + + return new DefaultAsyncHttpClientConfig( + followRedirect, + maxRedirects, + strict302Handling, + compressionEnforced, + enableAutomaticDecompression, + userAgent, + realm, + maxRequestRetry, + disableUrlEncodingForBoundRequests, + useLaxCookieEncoder, + disableZeroCopy, + keepEncodingHeader, + resolveProxyServerSelector(), + validateResponseHeaders, + aggregateWebSocketFrameFragments, + enablewebSocketCompression, + stripAuthorizationOnRedirect, + connectTimeout, + requestTimeout, + readTimeout, + shutdownQuietPeriod, + shutdownTimeout, + keepAlive, + pooledConnectionIdleTimeout, + connectionPoolCleanerPeriod, + connectionTtl, + maxConnections, + maxConnectionsPerHost, + acquireFreeChannelTimeout, + channelPool, + connectionSemaphoreFactory, + keepAliveStrategy, + useOpenSsl, + useInsecureTrustManager, + disableHttpsEndpointIdentificationAlgorithm, + handshakeTimeout, + enabledProtocols, + enabledCipherSuites, + filterInsecureCipherSuites, + sslSessionCacheSize, + sslSessionTimeout, + sslContext, + sslEngineFactory, + requestFilters.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(requestFilters), + responseFilters.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(responseFilters), + ioExceptionFilters.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(ioExceptionFilters), + cookieStore, + expiredCookieEvictionDelay, + tcpNoDelay, + soReuseAddress, + soKeepAlive, + soLinger, + soSndBuf, + soRcvBuf, + threadPoolName, + httpClientCodecMaxInitialLineLength, + httpClientCodecMaxHeaderSize, + httpClientCodecMaxChunkSize, + httpClientCodecInitialBufferSize, + chunkedFileChunkSize, + webSocketMaxBufferSize, + webSocketMaxFrameSize, + channelOptions.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(channelOptions), + eventLoopGroup, + useNativeTransport, + useOnlyEpollNativeTransport, + allocator, + nettyTimer, + threadFactory, + httpAdditionalChannelInitializer, + wsAdditionalChannelInitializer, + responseBodyPartFactory, + ioThreadsCount, + hashedWheelTickDuration, + hashedWheelSize); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/DefaultRequest.java b/client/src/main/java/org/asynchttpclient/DefaultRequest.java new file mode 100644 index 0000000000..09c615d2a2 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/DefaultRequest.java @@ -0,0 +1,311 @@ +/* + * 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; + +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; +import org.asynchttpclient.channel.ChannelPoolPartitioning; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.request.body.generator.BodyGenerator; +import org.asynchttpclient.request.body.multipart.Part; +import org.asynchttpclient.uri.Uri; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.InputStream; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; + +public class DefaultRequest implements Request { + + public final @Nullable ProxyServer proxyServer; + private final String method; + private final Uri uri; + private final @Nullable InetAddress address; + private final @Nullable InetAddress localAddress; + private final HttpHeaders headers; + private final List cookies; + private final byte @Nullable [] byteData; + 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; + private final List bodyParts; + private final @Nullable String virtualHost; + private final @Nullable Realm realm; + private final @Nullable File file; + private final @Nullable Boolean followRedirect; + private final Duration requestTimeout; + private final Duration readTimeout; + private final long rangeOffset; + private final @Nullable Charset charset; + private final ChannelPoolPartitioning channelPoolPartitioning; + private final NameResolver nameResolver; + + // lazily loaded + private @Nullable List queryParams; + + public DefaultRequest(String method, + Uri uri, + @Nullable InetAddress address, + @Nullable InetAddress localAddress, + HttpHeaders headers, + List cookies, + byte @Nullable [] byteData, + @Nullable List compositeByteData, + @Nullable String stringData, + @Nullable ByteBuffer byteBufferData, + @Nullable ByteBuf byteBufData, + @Nullable InputStream streamData, + @Nullable BodyGenerator bodyGenerator, + List formParams, + List bodyParts, + @Nullable String virtualHost, + @Nullable ProxyServer proxyServer, + @Nullable Realm realm, + @Nullable File file, + @Nullable Boolean followRedirect, + @Nullable Duration requestTimeout, + @Nullable Duration readTimeout, + long rangeOffset, + @Nullable Charset charset, + ChannelPoolPartitioning channelPoolPartitioning, + NameResolver nameResolver) { + this.method = method; + this.uri = uri; + this.address = address; + this.localAddress = localAddress; + this.headers = headers; + this.cookies = cookies; + this.byteData = byteData; + this.compositeByteData = compositeByteData; + this.stringData = stringData; + this.byteBufferData = byteBufferData; + this.byteBufData = byteBufData; + this.streamData = streamData; + this.bodyGenerator = bodyGenerator; + this.formParams = formParams; + this.bodyParts = bodyParts; + this.virtualHost = virtualHost; + this.proxyServer = proxyServer; + this.realm = realm; + this.file = file; + this.followRedirect = followRedirect; + this.requestTimeout = requestTimeout == null ? Duration.ZERO : requestTimeout; + this.readTimeout = readTimeout == null ? Duration.ZERO : readTimeout; + this.rangeOffset = rangeOffset; + this.charset = charset; + this.channelPoolPartitioning = channelPoolPartitioning; + this.nameResolver = nameResolver; + } + + @Override + public String getUrl() { + return uri.toUrl(); + } + + @Override + public String getMethod() { + return method; + } + + @Override + public Uri getUri() { + return uri; + } + + @Override + public @Nullable InetAddress getAddress() { + return address; + } + + @Override + public @Nullable InetAddress getLocalAddress() { + return localAddress; + } + + @Override + public HttpHeaders getHeaders() { + return headers; + } + + @Override + public List getCookies() { + return cookies; + } + + @Override + public byte @Nullable [] getByteData() { + return byteData; + } + + @Override + public @Nullable List getCompositeByteData() { + return compositeByteData; + } + + @Override + public @Nullable String getStringData() { + return stringData; + } + + @Override + public @Nullable ByteBuffer getByteBufferData() { + return byteBufferData; + } + + @Override + public @Nullable ByteBuf getByteBufData() { + return byteBufData; + } + + @Override + public @Nullable InputStream getStreamData() { + return streamData; + } + + @Override + public @Nullable BodyGenerator getBodyGenerator() { + return bodyGenerator; + } + + @Override + public List getFormParams() { + return formParams; + } + + @Override + public List getBodyParts() { + return bodyParts; + } + + @Override + public @Nullable String getVirtualHost() { + return virtualHost; + } + + @Override + public @Nullable ProxyServer getProxyServer() { + return proxyServer; + } + + @Override + public @Nullable Realm getRealm() { + return realm; + } + + @Override + public @Nullable File getFile() { + return file; + } + + @Override + public @Nullable Boolean getFollowRedirect() { + return followRedirect; + } + + @Override + public Duration getRequestTimeout() { + return requestTimeout; + } + + @Override + public Duration getReadTimeout() { + return readTimeout; + } + + @Override + public long getRangeOffset() { + return rangeOffset; + } + + @Override + public @Nullable Charset getCharset() { + return charset; + } + + @Override + public ChannelPoolPartitioning getChannelPoolPartitioning() { + return channelPoolPartitioning; + } + + @Override + public NameResolver getNameResolver() { + return nameResolver; + } + + @Override + public List getQueryParams() { + // lazy load + if (queryParams == null) { + if (isNonEmpty(uri.getQuery())) { + queryParams = new ArrayList<>(1); + for (String queryStringParam : uri.getQuery().split("&")) { + int pos = queryStringParam.indexOf('='); + if (pos <= 0) { + queryParams.add(new Param(queryStringParam, null)); + } else { + queryParams.add(new Param(queryStringParam.substring(0, pos), queryStringParam.substring(pos + 1))); + } + } + } else { + queryParams = Collections.emptyList(); + } + } + return queryParams; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(getUrl()); + sb.append('\t'); + sb.append(method); + sb.append("\theaders:"); + + if (!headers.isEmpty()) { + for (Map.Entry header : headers) { + sb.append('\t'); + sb.append(header.getKey()); + sb.append(':'); + sb.append(header.getValue()); + } + } + + if (isNonEmpty(formParams)) { + sb.append("\tformParams:"); + for (Param param : formParams) { + sb.append('\t'); + sb.append(param.getName()); + sb.append(':'); + sb.append(param.getValue()); + } + } + return sb.toString(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/Dsl.java b/client/src/main/java/org/asynchttpclient/Dsl.java new file mode 100644 index 0000000000..f72820258c --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/Dsl.java @@ -0,0 +1,133 @@ +/* + * 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; + +import org.asynchttpclient.Realm.AuthScheme; +import org.asynchttpclient.proxy.ProxyServer; + +import static org.asynchttpclient.util.HttpConstants.Methods.DELETE; +import static org.asynchttpclient.util.HttpConstants.Methods.GET; +import static org.asynchttpclient.util.HttpConstants.Methods.HEAD; +import static org.asynchttpclient.util.HttpConstants.Methods.OPTIONS; +import static org.asynchttpclient.util.HttpConstants.Methods.PATCH; +import static org.asynchttpclient.util.HttpConstants.Methods.POST; +import static org.asynchttpclient.util.HttpConstants.Methods.PUT; +import static org.asynchttpclient.util.HttpConstants.Methods.TRACE; + +public final class Dsl { + + private Dsl() { + } + + // /////////// Client //////////////// + public static AsyncHttpClient asyncHttpClient() { + return new DefaultAsyncHttpClient(); + } + + public static AsyncHttpClient asyncHttpClient(DefaultAsyncHttpClientConfig.Builder configBuilder) { + return new DefaultAsyncHttpClient(configBuilder.build()); + } + + public static AsyncHttpClient asyncHttpClient(AsyncHttpClientConfig config) { + return new DefaultAsyncHttpClient(config); + } + + // /////////// Request //////////////// + public static RequestBuilder get(String url) { + return request(GET, url); + } + + public static RequestBuilder put(String url) { + return request(PUT, url); + } + + public static RequestBuilder post(String url) { + return request(POST, url); + } + + public static RequestBuilder delete(String url) { + return request(DELETE, url); + } + + public static RequestBuilder head(String url) { + return request(HEAD, url); + } + + public static RequestBuilder options(String url) { + return request(OPTIONS, url); + } + + public static RequestBuilder patch(String url) { + return request(PATCH, url); + } + + public static RequestBuilder trace(String url) { + return request(TRACE, url); + } + + public static RequestBuilder request(String method, String url) { + return new RequestBuilder(method).setUrl(url); + } + + // /////////// ProxyServer //////////////// + public static ProxyServer.Builder proxyServer(String host, int port) { + return new ProxyServer.Builder(host, port); + } + + // /////////// Config //////////////// + public static DefaultAsyncHttpClientConfig.Builder config() { + return new DefaultAsyncHttpClientConfig.Builder(); + } + + // /////////// Realm //////////////// + public static Realm.Builder realm(Realm prototype) { + return new Realm.Builder(prototype.getPrincipal(), prototype.getPassword()) + .setRealmName(prototype.getRealmName()) + .setAlgorithm(prototype.getAlgorithm()) + .setNc(prototype.getNc()) + .setNonce(prototype.getNonce()) + .setCharset(prototype.getCharset()) + .setOpaque(prototype.getOpaque()) + .setQop(prototype.getQop()) + .setScheme(prototype.getScheme()) + .setUri(prototype.getUri()) + .setUsePreemptiveAuth(prototype.isUsePreemptiveAuth()) + .setNtlmDomain(prototype.getNtlmDomain()) + .setNtlmHost(prototype.getNtlmHost()) + .setUseAbsoluteURI(prototype.isUseAbsoluteURI()) + .setOmitQuery(prototype.isOmitQuery()) + .setServicePrincipalName(prototype.getServicePrincipalName()) + .setUseCanonicalHostname(prototype.isUseCanonicalHostname()) + .setCustomLoginConfig(prototype.getCustomLoginConfig()) + .setLoginContextName(prototype.getLoginContextName()); + } + + public static Realm.Builder realm(AuthScheme scheme, String principal, String password) { + return new Realm.Builder(principal, password).setScheme(scheme); + } + + public static Realm.Builder basicAuthRealm(String principal, String password) { + return realm(AuthScheme.BASIC, principal, password); + } + + public static Realm.Builder digestAuthRealm(String principal, String password) { + return realm(AuthScheme.DIGEST, principal, password); + } + + public static Realm.Builder ntlmAuthRealm(String principal, String password) { + return realm(AuthScheme.NTLM, principal, password); + } +} diff --git a/client/src/main/java/org/asynchttpclient/HostStats.java b/client/src/main/java/org/asynchttpclient/HostStats.java new file mode 100644 index 0000000000..3470ea4e1e --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/HostStats.java @@ -0,0 +1,78 @@ +/* + * 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. + * 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 java.util.Objects; + +/** + * A record class representing the status of connections to some host. + */ +public class HostStats { + + private final long activeConnectionCount; + private final long idleConnectionCount; + + public HostStats(long activeConnectionCount, long idleConnectionCount) { + this.activeConnectionCount = activeConnectionCount; + this.idleConnectionCount = idleConnectionCount; + } + + /** + * @return The sum of {@link #getHostActiveConnectionCount()} and {@link #getHostIdleConnectionCount()}, + * a long representing the total number of connections to this host. + */ + public long getHostConnectionCount() { + return activeConnectionCount + idleConnectionCount; + } + + /** + * @return A long representing the number of active connections to the host. + */ + public long getHostActiveConnectionCount() { + return activeConnectionCount; + } + + /** + * @return A long representing the number of idle connections in the connection pool. + */ + public long getHostIdleConnectionCount() { + return idleConnectionCount; + } + + @Override + public String toString() { + return "There are " + getHostConnectionCount() + + " total connections, " + getHostActiveConnectionCount() + + " are active and " + getHostIdleConnectionCount() + " are idle."; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final HostStats hostStats = (HostStats) o; + return activeConnectionCount == hostStats.activeConnectionCount && idleConnectionCount == hostStats.idleConnectionCount; + } + + @Override + public int hashCode() { + return Objects.hash(activeConnectionCount, idleConnectionCount); + } +} diff --git a/client/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java b/client/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java new file mode 100644 index 0000000000..0df78f7b2c --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java @@ -0,0 +1,61 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.netty.buffer.ByteBuf; + +import java.nio.ByteBuffer; + +/** + * A callback class used when an HTTP response body is received. + */ +public abstract class HttpResponseBodyPart { + + private final boolean last; + + protected HttpResponseBodyPart(boolean last) { + this.last = last; + } + + /** + * @return length of this part in bytes + */ + public abstract int length(); + + /** + * @return the response body's part bytes received. + */ + public abstract byte[] getBodyPartBytes(); + + /** + * @return a {@link ByteBuffer} that wraps the actual bytes read from the response's chunk. + * The {@link ByteBuffer}'s capacity is equal to the number of bytes available. + */ + 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. + */ + public boolean isLast() { + return last; + } +} diff --git a/client/src/main/java/org/asynchttpclient/HttpResponseStatus.java b/client/src/main/java/org/asynchttpclient/HttpResponseStatus.java new file mode 100644 index 0000000000..8ac5c316d8 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/HttpResponseStatus.java @@ -0,0 +1,108 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 org.asynchttpclient.uri.Uri; + +import java.net.SocketAddress; + +/** + * A class that represent the HTTP response status line (code + text) + */ +public abstract class HttpResponseStatus { + + private final Uri uri; + + protected HttpResponseStatus(Uri uri) { + this.uri = uri; + } + + /** + * Return the request {@link Uri} + * + * @return the request {@link Uri} + */ + public Uri getUri() { + return uri; + } + + /** + * Return the response status code + * + * @return the response status code + */ + public abstract int getStatusCode(); + + /** + * Return the response status text + * + * @return the response status text + */ + public abstract String getStatusText(); + + /** + * Protocol name from status line. + * + * @return Protocol name. + */ + public abstract String getProtocolName(); + + /** + * Protocol major version. + * + * @return Major version. + */ + public abstract int getProtocolMajorVersion(); + + /** + * Protocol minor version. + * + * @return Minor version. + */ + public abstract int getProtocolMinorVersion(); + + /** + * Full protocol name + version + * + * @return protocol name + version + */ + public abstract String getProtocolText(); + + /** + * Get remote address client initiated request to. + * + * @return remote address client initiated request to, may be {@code null} + * if asynchronous provider is unable to provide the remote address + */ + public abstract SocketAddress getRemoteAddress(); + + /** + * Get local address client initiated request from. + * + * @return local address client initiated request from, may be {@code null} + * if asynchronous provider is unable to provide the local address + */ + public abstract SocketAddress getLocalAddress(); + + /** + * Code followed by text. + */ + @Override + public String toString() { + return getStatusCode() + " " + getStatusText(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/ListenableFuture.java b/client/src/main/java/org/asynchttpclient/ListenableFuture.java new file mode 100755 index 0000000000..6f9280369c --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/ListenableFuture.java @@ -0,0 +1,151 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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. + */ +/* + * Copyright (C) 2007 Google Inc. + * + * 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 java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * Extended {@link Future} + * + * @param Type of the value that will be returned. + */ +public interface ListenableFuture extends Future { + + /** + * Terminate and if there is no exception, mark this Future as done and release the internal lock. + */ + void done(); + + /** + * Abort the current processing, and propagate the {@link Throwable} to the {@link AsyncHandler} or {@link Future} + * + * @param t the exception + */ + void abort(Throwable t); + + /** + * Touch the current instance to prevent external service to times out. + */ + void touch(); + + /** + * Adds a listener and executor to the ListenableFuture. + * The listener will be {@linkplain Executor#execute(Runnable) passed + * to the executor} for execution when the {@code Future}'s computation is + * {@linkplain Future#isDone() complete}. + *
+ * Executor can be {@code null}, in that case executor will be executed + * 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, + * but any listener added through this method is guaranteed to be called once + * the computation is complete. + * + * @param listener the listener to run when the computation is complete. + * @param exec the executor to run the listener in. + * @return this Future + */ + ListenableFuture addListener(Runnable listener, Executor exec); + + CompletableFuture toCompletableFuture(); + + class CompletedFailure implements ListenableFuture { + + private final ExecutionException e; + + public CompletedFailure(Throwable t) { + e = new ExecutionException(t); + } + + public CompletedFailure(String message, Throwable t) { + e = new ExecutionException(message, t); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return true; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public T get() throws ExecutionException { + throw e; + } + + @Override + public T get(long timeout, TimeUnit unit) throws ExecutionException { + throw e; + } + + @Override + public void done() { + } + + @Override + public void abort(Throwable t) { + } + + @Override + public void touch() { + } + + @Override + public ListenableFuture addListener(Runnable listener, Executor exec) { + if (exec != null) { + exec.execute(listener); + } else { + listener.run(); + } + return this; + } + + @Override + public CompletableFuture toCompletableFuture() { + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(e); + return future; + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/Param.java b/client/src/main/java/org/asynchttpclient/Param.java new file mode 100644 index 0000000000..4f7a5530ae --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/Param.java @@ -0,0 +1,96 @@ +/* + * 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. + * 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 org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * A pair of (name, value) String + * + * @author slandelle + */ +public class Param { + + private final String name; + private final @Nullable String value; + + public Param(String name, @Nullable String value) { + this.name = name; + this.value = value; + } + + public static @Nullable List map2ParamList(Map> map) { + if (map == null) { + return null; + } + + List params = new ArrayList<>(map.size()); + for (Map.Entry> entries : map.entrySet()) { + String name = entries.getKey(); + for (String value : entries.getValue()) { + params.add(new Param(name, value)); + } + } + return params; + } + + public String getName() { + return name; + } + + public @Nullable String getValue() { + return value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (name == null ? 0 : name.hashCode()); + result = prime * result + (value == null ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Param)) { + return false; + } + Param other = (Param) obj; + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (value == null) { + return other.value == null; + } else { + return value.equals(other.value); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/Realm.java b/client/src/main/java/org/asynchttpclient/Realm.java new file mode 100644 index 0000000000..c6b70a7dee --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/Realm.java @@ -0,0 +1,599 @@ +/* + * Copyright 2010-2013 Ning, Inc. + * + * This program is licensed 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: + * + * 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 org.asynchttpclient.uri.Uri; +import org.asynchttpclient.util.AuthenticatorUtils; +import org.asynchttpclient.util.StringBuilderPool; +import org.asynchttpclient.util.StringUtils; +import org.jetbrains.annotations.Nullable; + +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; +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; +import static org.asynchttpclient.util.StringUtils.appendBase16; +import static org.asynchttpclient.util.StringUtils.toHexString; + +/** + * This class is required when authentication is needed. The class support + * BASIC, DIGEST, NTLM, SPNEGO and KERBEROS. + */ +public class Realm { + + private static final String DEFAULT_NC = "00000001"; + // MD5("") + private static final String EMPTY_ENTITY_MD5 = "d41d8cd98f00b204e9800998ecf8427e"; + + private final @Nullable String principal; + private final @Nullable String password; + private final AuthScheme scheme; + private final @Nullable String realmName; + private final @Nullable String nonce; + private final @Nullable String algorithm; + private final @Nullable String response; + private final @Nullable String opaque; + private final @Nullable String qop; + private final String nc; + private final @Nullable String cnonce; + private final @Nullable Uri uri; + private final boolean usePreemptiveAuth; + private final Charset charset; + private final String ntlmHost; + private final String ntlmDomain; + private final boolean useAbsoluteURI; + private final boolean omitQuery; + private final @Nullable Map customLoginConfig; + private final @Nullable String servicePrincipalName; + private final boolean useCanonicalHostname; + private final @Nullable String loginContextName; + + private Realm(@Nullable AuthScheme scheme, + @Nullable String principal, + @Nullable String password, + @Nullable String realmName, + @Nullable String nonce, + @Nullable String algorithm, + @Nullable String response, + @Nullable String opaque, + @Nullable String qop, + String nc, + @Nullable String cnonce, + @Nullable Uri uri, + boolean usePreemptiveAuth, + Charset charset, + String ntlmDomain, + String ntlmHost, + boolean useAbsoluteURI, + boolean omitQuery, + @Nullable String servicePrincipalName, + boolean useCanonicalHostname, + @Nullable Map customLoginConfig, + @Nullable String loginContextName) { + + this.scheme = requireNonNull(scheme, "scheme"); + this.principal = principal; + this.password = password; + this.realmName = realmName; + this.nonce = nonce; + this.algorithm = algorithm; + this.response = response; + this.opaque = opaque; + this.qop = qop; + this.nc = nc; + this.cnonce = cnonce; + this.uri = uri; + this.usePreemptiveAuth = usePreemptiveAuth; + this.charset = charset; + this.ntlmDomain = ntlmDomain; + this.ntlmHost = ntlmHost; + this.useAbsoluteURI = useAbsoluteURI; + this.omitQuery = omitQuery; + this.servicePrincipalName = servicePrincipalName; + this.useCanonicalHostname = useCanonicalHostname; + this.customLoginConfig = customLoginConfig; + this.loginContextName = loginContextName; + } + + public @Nullable String getPrincipal() { + return principal; + } + + public @Nullable String getPassword() { + return password; + } + + public AuthScheme getScheme() { + return scheme; + } + + public @Nullable String getRealmName() { + return realmName; + } + + public @Nullable String getNonce() { + return nonce; + } + + public @Nullable String getAlgorithm() { + return algorithm; + } + + public @Nullable String getResponse() { + return response; + } + + public @Nullable String getOpaque() { + return opaque; + } + + public @Nullable String getQop() { + return qop; + } + + public String getNc() { + return nc; + } + + public @Nullable String getCnonce() { + return cnonce; + } + + public @Nullable Uri getUri() { + return uri; + } + + public Charset getCharset() { + return charset; + } + + /** + * Return true is preemptive authentication is enabled + * + * @return true is preemptive authentication is enabled + */ + public boolean isUsePreemptiveAuth() { + return usePreemptiveAuth; + } + + /** + * Return the NTLM domain to use. This value should map the JDK + * + * @return the NTLM domain + */ + public String getNtlmDomain() { + return ntlmDomain; + } + + /** + * Return the NTLM host. + * + * @return the NTLM host + */ + public String getNtlmHost() { + return ntlmHost; + } + + public boolean isUseAbsoluteURI() { + return useAbsoluteURI; + } + + public boolean isOmitQuery() { + return omitQuery; + } + + public @Nullable Map getCustomLoginConfig() { + return customLoginConfig; + } + + public @Nullable String getServicePrincipalName() { + return servicePrincipalName; + } + + public boolean isUseCanonicalHostname() { + return useCanonicalHostname; + } + + public @Nullable String getLoginContextName() { + return loginContextName; + } + + @Override + public String toString() { + return "Realm{" + + "principal='" + principal + '\'' + + ", password='" + password + '\'' + + ", scheme=" + scheme + + ", realmName='" + realmName + '\'' + + ", nonce='" + nonce + '\'' + + ", algorithm='" + algorithm + '\'' + + ", response='" + response + '\'' + + ", opaque='" + opaque + '\'' + + ", qop='" + qop + '\'' + + ", nc='" + nc + '\'' + + ", cnonce='" + cnonce + '\'' + + ", uri=" + uri + + ", usePreemptiveAuth=" + usePreemptiveAuth + + ", charset=" + charset + + ", ntlmHost='" + ntlmHost + '\'' + + ", ntlmDomain='" + ntlmDomain + '\'' + + ", useAbsoluteURI=" + useAbsoluteURI + + ", omitQuery=" + omitQuery + + ", customLoginConfig=" + customLoginConfig + + ", servicePrincipalName='" + servicePrincipalName + '\'' + + ", useCanonicalHostname=" + useCanonicalHostname + + ", loginContextName='" + loginContextName + '\'' + + '}'; + } + + public enum AuthScheme { + BASIC, DIGEST, NTLM, SPNEGO, KERBEROS + } + + /** + * A builder for {@link Realm} + */ + public static class Builder { + + private final @Nullable String principal; + private final @Nullable String password; + private @Nullable AuthScheme scheme; + private @Nullable String realmName; + private @Nullable String nonce; + private @Nullable String algorithm; + private @Nullable String response; + private @Nullable String opaque; + private @Nullable String qop; + private String nc = DEFAULT_NC; + private @Nullable String cnonce; + private @Nullable Uri uri; + private String methodName = GET; + private boolean usePreemptive; + private String ntlmDomain = System.getProperty("http.auth.ntlm.domain"); + private Charset charset = UTF_8; + private String ntlmHost = "localhost"; + private boolean useAbsoluteURI; + private boolean omitQuery; + /** + * Kerberos/Spnego properties + */ + private @Nullable Map customLoginConfig; + private @Nullable String servicePrincipalName; + private boolean useCanonicalHostname; + private @Nullable String loginContextName; + + public Builder() { + principal = null; + password = null; + } + + public Builder(@Nullable String principal, @Nullable String password) { + this.principal = principal; + this.password = password; + } + + public Builder setNtlmDomain(String ntlmDomain) { + this.ntlmDomain = ntlmDomain; + return this; + } + + public Builder setNtlmHost(String host) { + ntlmHost = host; + return this; + } + + public Builder setScheme(AuthScheme scheme) { + this.scheme = scheme; + return this; + } + + public Builder setRealmName(@Nullable String realmName) { + this.realmName = realmName; + return this; + } + + public Builder setNonce(@Nullable String nonce) { + this.nonce = nonce; + return this; + } + + public Builder setAlgorithm(@Nullable String algorithm) { + this.algorithm = algorithm; + return this; + } + + public Builder setResponse(String response) { + this.response = response; + return this; + } + + public Builder setOpaque(@Nullable String opaque) { + this.opaque = opaque; + return this; + } + + public Builder setQop(@Nullable String qop) { + if (isNonEmpty(qop)) { + this.qop = qop; + } + return this; + } + + public Builder setNc(String nc) { + this.nc = nc; + return this; + } + + public Builder setUri(@Nullable Uri uri) { + this.uri = uri; + return this; + } + + public Builder setMethodName(String methodName) { + this.methodName = methodName; + return this; + } + + public Builder setUsePreemptiveAuth(boolean usePreemptiveAuth) { + usePreemptive = usePreemptiveAuth; + return this; + } + + public Builder setUseAbsoluteURI(boolean useAbsoluteURI) { + this.useAbsoluteURI = useAbsoluteURI; + return this; + } + + public Builder setOmitQuery(boolean omitQuery) { + this.omitQuery = omitQuery; + return this; + } + + public Builder setCharset(Charset charset) { + this.charset = charset; + return this; + } + + public Builder setCustomLoginConfig(@Nullable Map customLoginConfig) { + this.customLoginConfig = customLoginConfig; + return this; + } + + public Builder setServicePrincipalName(@Nullable String servicePrincipalName) { + this.servicePrincipalName = servicePrincipalName; + return this; + } + + public Builder setUseCanonicalHostname(boolean useCanonicalHostname) { + this.useCanonicalHostname = useCanonicalHostname; + return this; + } + + public Builder setLoginContextName(@Nullable String loginContextName) { + this.loginContextName = loginContextName; + return this; + } + + private static @Nullable String parseRawQop(String rawQop) { + String[] rawServerSupportedQops = rawQop.split(","); + String[] serverSupportedQops = new String[rawServerSupportedQops.length]; + for (int i = 0; i < rawServerSupportedQops.length; i++) { + serverSupportedQops[i] = rawServerSupportedQops[i].trim(); + } + + // prefer auth over auth-int + for (String rawServerSupportedQop : serverSupportedQops) { + if ("auth".equals(rawServerSupportedQop)) { + return rawServerSupportedQop; + } + } + + for (String rawServerSupportedQop : serverSupportedQops) { + if ("auth-int".equals(rawServerSupportedQop)) { + return rawServerSupportedQop; + } + } + + return null; + } + + public Builder parseWWWAuthenticateHeader(String headerLine) { + setRealmName(match(headerLine, "realm")) + .setNonce(match(headerLine, "nonce")) + .setOpaque(match(headerLine, "opaque")) + .setScheme(isNonEmpty(nonce) ? AuthScheme.DIGEST : AuthScheme.BASIC); + String algorithm = match(headerLine, "algorithm"); + if (isNonEmpty(algorithm)) { + setAlgorithm(algorithm); + } + + // FIXME qop is different with proxy? + String rawQop = match(headerLine, "qop"); + if (rawQop != null) { + setQop(parseRawQop(rawQop)); + } + + return this; + } + + public Builder parseProxyAuthenticateHeader(String headerLine) { + setRealmName(match(headerLine, "realm")) + .setNonce(match(headerLine, "nonce")) + .setOpaque(match(headerLine, "opaque")) + .setScheme(isNonEmpty(nonce) ? AuthScheme.DIGEST : AuthScheme.BASIC); + String algorithm = match(headerLine, "algorithm"); + if (isNonEmpty(algorithm)) { + setAlgorithm(algorithm); + } + // FIXME qop is different with proxy? + setQop(match(headerLine, "qop")); + + return this; + } + + private void newCnonce(MessageDigest md) { + byte[] b = new byte[8]; + ThreadLocalRandom.current().nextBytes(b); + b = md.digest(b); + cnonce = toHexString(b); + } + + /** + * TODO: A Pattern/Matcher may be better. + */ + private static @Nullable String match(String headerLine, String token) { + if (headerLine == null) { + return null; + } + + int match = headerLine.indexOf(token); + if (match <= 0) { + return null; + } + + // = to skip + match += token.length() + 1; + int trailingComa = headerLine.indexOf(',', match); + String value = headerLine.substring(match, trailingComa > 0 ? trailingComa : headerLine.length()); + value = value.length() > 0 && value.charAt(value.length() - 1) == '"' + ? value.substring(0, value.length() - 1) + : value; + return value.charAt(0) == '"' ? value.substring(1) : value; + } + + private static byte[] md5FromRecycledStringBuilder(StringBuilder sb, MessageDigest md) { + md.update(StringUtils.charSequence2ByteBuffer(sb, ISO_8859_1)); + sb.setLength(0); + return md.digest(); + } + + private byte[] ha1(StringBuilder sb, MessageDigest md) { + // if algorithm is "MD5" or is unspecified => A1 = username ":" realm-value ":" + // passwd + // if algorithm is "MD5-sess" => A1 = MD5( username-value ":" realm-value ":" + // passwd ) ":" nonce-value ":" cnonce-value + + sb.append(principal).append(':').append(realmName).append(':').append(password); + byte[] core = md5FromRecycledStringBuilder(sb, md); + + if (algorithm == null || "MD5".equals(algorithm)) { + // A1 = username ":" realm-value ":" passwd + return core; + } + if ("MD5-sess".equals(algorithm)) { + // A1 = MD5(username ":" realm-value ":" passwd ) ":" nonce ":" cnonce + appendBase16(sb, core); + sb.append(':').append(nonce).append(':').append(cnonce); + return md5FromRecycledStringBuilder(sb, md); + } + + throw new UnsupportedOperationException("Digest algorithm not supported: " + algorithm); + } + + private byte[] ha2(StringBuilder sb, String digestUri, MessageDigest md) { + + // if qop is "auth" or is unspecified => A2 = Method ":" digest-uri-value + // if qop is "auth-int" => A2 = Method ":" digest-uri-value ":" H(entity-body) + sb.append(methodName).append(':').append(digestUri); + if ("auth-int".equals(qop)) { + // when qop == "auth-int", A2 = Method ":" digest-uri-value ":" H(entity-body) + // but we don't have the request body here + // we would need a new API + sb.append(':').append(EMPTY_ENTITY_MD5); + + } else if (qop != null && !"auth".equals(qop)) { + throw new UnsupportedOperationException("Digest qop not supported: " + qop); + } + + return md5FromRecycledStringBuilder(sb, md); + } + + private void appendMiddlePart(StringBuilder sb) { + // request-digest = MD5(H(A1) ":" nonce ":" nc ":" cnonce ":" qop ":" H(A2)) + sb.append(':').append(nonce).append(':'); + if ("auth".equals(qop) || "auth-int".equals(qop)) { + sb.append(nc).append(':').append(cnonce).append(':').append(qop).append(':'); + } + } + + private void newResponse(MessageDigest md) { + // when using preemptive auth, the request uri is missing + if (uri != null) { + // BEWARE: compute first as it uses the cached StringBuilder + String digestUri = AuthenticatorUtils.computeRealmURI(uri, useAbsoluteURI, omitQuery); + + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); + + // WARNING: DON'T MOVE, BUFFER IS RECYCLED!!!! + byte[] ha1 = ha1(sb, md); + byte[] ha2 = ha2(sb, digestUri, md); + + appendBase16(sb, ha1); + appendMiddlePart(sb); + appendBase16(sb, ha2); + + byte[] responseDigest = md5FromRecycledStringBuilder(sb, md); + response = toHexString(responseDigest); + } + } + + /** + * Build a {@link Realm} + * + * @return a {@link Realm} + */ + public Realm build() { + + // Avoid generating + if (isNonEmpty(nonce)) { + MessageDigest md = pooledMd5MessageDigest(); + newCnonce(md); + newResponse(md); + } + + return new Realm(scheme, + principal, + password, + realmName, + nonce, + algorithm, + response, + opaque, + qop, + nc, + cnonce, + uri, + usePreemptive, + charset, + ntlmDomain, + ntlmHost, + useAbsoluteURI, + omitQuery, + servicePrincipalName, + useCanonicalHostname, + customLoginConfig, + loginContextName); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/Request.java b/client/src/main/java/org/asynchttpclient/Request.java new file mode 100644 index 0000000000..1d95016b36 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/Request.java @@ -0,0 +1,212 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.cookie.Cookie; +import io.netty.resolver.NameResolver; +import org.asynchttpclient.channel.ChannelPoolPartitioning; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.request.body.generator.BodyGenerator; +import org.asynchttpclient.request.body.multipart.Part; +import org.asynchttpclient.uri.Uri; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.InputStream; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.time.Duration; +import java.util.List; + +/** + * The Request class can be used to construct HTTP request: + *
+ *   Request r = new RequestBuilder()
+ *      .setUrl("url")
+ *      .setRealm(
+ *          new Realm.Builder("principal", "password")
+ *              .setRealmName("MyRealm")
+ *              .setScheme(Realm.AuthScheme.BASIC)
+ *      ).build();
+ * 
+ */ +public interface Request { + + /** + * @return the request's HTTP method (GET, POST, etc.) + */ + String getMethod(); + + /** + * @return the uri + */ + Uri getUri(); + + /** + * @return the url (the uri's String form) + */ + String getUrl(); + + /** + * @return the InetAddress to be used to bypass uri's hostname resolution + */ + @Nullable + InetAddress getAddress(); + + /** + * @return the local address to bind from + */ + @Nullable + InetAddress getLocalAddress(); + + /** + * @return the HTTP headers + */ + HttpHeaders getHeaders(); + + /** + * @return the HTTP cookies + */ + List getCookies(); + + /** + * @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) + */ + @Nullable + List getCompositeByteData(); + + /** + * @return the request's body string (only non-null if it was set this way) + */ + @Nullable + String getStringData(); + + /** + * @return the request's body ByteBuffer (only non-null if it was set this way) + */ + @Nullable + ByteBuffer getByteBufferData(); + + /** + * @return the request's body ByteBuf (only non-null if it was set this way) + */ + @Nullable + ByteBuf getByteBufData(); + + /** + * @return the request's body InputStream (only non-null if it was set this way) + */ + @Nullable + InputStream getStreamData(); + + /** + * @return the request's body BodyGenerator (only non-null if it was set this way) + */ + @Nullable + BodyGenerator getBodyGenerator(); + + /** + * @return the request's form parameters + */ + List getFormParams(); + + /** + * @return the multipart parts + */ + List getBodyParts(); + + /** + * @return the virtual host to connect to + */ + @Nullable + String getVirtualHost(); + + /** + * @return the query params resolved from the url/uri + */ + List getQueryParams(); + + /** + * @return the proxy server to be used to perform this request (overrides the one defined in config) + */ + @Nullable + ProxyServer getProxyServer(); + + /** + * @return the realm to be used to perform this request (overrides the one defined in config) + */ + @Nullable + Realm getRealm(); + + /** + * @return the file to be uploaded + */ + @Nullable + File getFile(); + + /** + * @return if this request is to follow redirects. Non null values means "override config value". + */ + @Nullable + Boolean getFollowRedirect(); + + /** + * @return the request timeout. Non zero values means "override config value". + */ + Duration getRequestTimeout(); + + /** + * @return the read timeout. Non-zero values means "override config value". + */ + Duration getReadTimeout(); + + /** + * @return the range header value, or 0 is not set. + */ + long getRangeOffset(); + + /** + * @return the charset value used when decoding the request's body. + */ + @Nullable + Charset getCharset(); + + /** + * @return the strategy to compute ChannelPool's keys + */ + ChannelPoolPartitioning getChannelPoolPartitioning(); + + /** + * @return the NameResolver to be used to resolve hostnams's IP + */ + NameResolver getNameResolver(); + + /** + * @return a new request builder using this request as a prototype + */ + 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 new file mode 100644 index 0000000000..9b4491ffc3 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/RequestBuilder.java @@ -0,0 +1,45 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 static org.asynchttpclient.util.HttpConstants.Methods.GET; + +/** + * Builder for a {@link Request}. Warning: mutable and not thread-safe! Beware that it holds a reference to the Request instance it builds, so modifying the builder will modify the + * request even after it has been built. + */ +public class RequestBuilder extends RequestBuilderBase { + + public RequestBuilder() { + this(GET); + } + + public RequestBuilder(String method) { + this(method, false); + } + + public RequestBuilder(String method, boolean disableUrlEncoding) { + super(method, disableUrlEncoding); + } + + public RequestBuilder(String method, boolean disableUrlEncoding, boolean validateHeaders) { + super(method, disableUrlEncoding, validateHeaders); + } + + RequestBuilder(Request prototype) { + super(prototype); + } +} diff --git a/client/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/client/src/main/java/org/asynchttpclient/RequestBuilderBase.java new file mode 100644 index 0000000000..dbc5e41442 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -0,0 +1,693 @@ +/* + * Copyright 2010-2013 Ning, Inc. + * + * This program is licensed 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: + * + * 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 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; +import io.netty.resolver.DefaultNameResolver; +import io.netty.resolver.NameResolver; +import io.netty.util.concurrent.ImmediateEventExecutor; +import org.asynchttpclient.channel.ChannelPoolPartitioning; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.request.body.generator.BodyGenerator; +import org.asynchttpclient.request.body.multipart.Part; +import org.asynchttpclient.uri.Uri; +import org.asynchttpclient.util.EnsuresNonNull; +import org.asynchttpclient.util.UriEncoder; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.InputStream; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.asynchttpclient.util.HttpUtils.extractContentTypeCharsetAttribute; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.withDefault; + +/** + * Builder for {@link Request} + * + * @param the builder type + */ +public abstract class RequestBuilderBase> { + + private static final Logger LOGGER = LoggerFactory.getLogger(RequestBuilderBase.class); + private static final Uri DEFAULT_REQUEST_URL = Uri.create("http://localhost/"); + public static final NameResolver DEFAULT_NAME_RESOLVER = new DefaultNameResolver(ImmediateEventExecutor.INSTANCE); + // builder only fields + protected UriEncoder uriEncoder; + protected @Nullable List queryParams; + protected @Nullable SignatureCalculator signatureCalculator; + + // request fields + protected String method; + protected @Nullable Uri uri; + protected @Nullable InetAddress address; + protected @Nullable InetAddress localAddress; + protected HttpHeaders headers; + protected @Nullable ArrayList cookies; + protected byte @Nullable [] byteData; + 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; + protected @Nullable List bodyParts; + protected @Nullable String virtualHost; + protected @Nullable ProxyServer proxyServer; + protected @Nullable Realm realm; + protected @Nullable File file; + protected @Nullable Boolean followRedirect; + protected @Nullable Duration requestTimeout; + protected @Nullable Duration readTimeout; + protected long rangeOffset; + protected @Nullable Charset charset; + protected ChannelPoolPartitioning channelPoolPartitioning = ChannelPoolPartitioning.PerHostChannelPoolPartitioning.INSTANCE; + protected NameResolver nameResolver = DEFAULT_NAME_RESOLVER; + + protected RequestBuilderBase(String method, boolean disableUrlEncoding) { + this(method, disableUrlEncoding, true); + } + + protected RequestBuilderBase(String method, boolean disableUrlEncoding, boolean validateHeaders) { + this.method = method; + uriEncoder = UriEncoder.uriEncoder(disableUrlEncoding); + headers = new DefaultHttpHeaders(validateHeaders); + } + + protected RequestBuilderBase(Request prototype) { + this(prototype, false, false); + } + + protected RequestBuilderBase(Request prototype, boolean disableUrlEncoding, boolean validateHeaders) { + method = prototype.getMethod(); + uriEncoder = UriEncoder.uriEncoder(disableUrlEncoding); + uri = prototype.getUri(); + address = prototype.getAddress(); + localAddress = prototype.getLocalAddress(); + headers = new DefaultHttpHeaders(validateHeaders); + headers.add(prototype.getHeaders()); + if (isNonEmpty(prototype.getCookies())) { + cookies = new ArrayList<>(prototype.getCookies()); + } + byteData = prototype.getByteData(); + compositeByteData = prototype.getCompositeByteData(); + stringData = prototype.getStringData(); + byteBufferData = prototype.getByteBufferData(); + byteBufData = prototype.getByteBufData(); + streamData = prototype.getStreamData(); + bodyGenerator = prototype.getBodyGenerator(); + if (isNonEmpty(prototype.getFormParams())) { + formParams = new ArrayList<>(prototype.getFormParams()); + } + if (isNonEmpty(prototype.getBodyParts())) { + bodyParts = new ArrayList<>(prototype.getBodyParts()); + } + virtualHost = prototype.getVirtualHost(); + proxyServer = prototype.getProxyServer(); + realm = prototype.getRealm(); + file = prototype.getFile(); + followRedirect = prototype.getFollowRedirect(); + requestTimeout = prototype.getRequestTimeout(); + readTimeout = prototype.getReadTimeout(); + rangeOffset = prototype.getRangeOffset(); + charset = prototype.getCharset(); + channelPoolPartitioning = prototype.getChannelPoolPartitioning(); + nameResolver = prototype.getNameResolver(); + } + + @SuppressWarnings("unchecked") + private T asDerivedType() { + return (T) this; + } + + public T setUrl(String url) { + return setUri(Uri.create(url)); + } + + public T setUri(Uri uri) { + this.uri = uri; + return asDerivedType(); + } + + public T setAddress(InetAddress address) { + this.address = address; + return asDerivedType(); + } + + public T setLocalAddress(InetAddress address) { + localAddress = address; + return asDerivedType(); + } + + public T setVirtualHost(String virtualHost) { + this.virtualHost = virtualHost; + return asDerivedType(); + } + + /** + * Remove all added headers + * + * @return {@code this} + */ + public T clearHeaders() { + headers.clear(); + return asDerivedType(); + } + + /** + * @param name header name + * @param value header value to set + * @return {@code this} + * @see #setHeader(CharSequence, Object) + */ + public T setHeader(CharSequence name, String value) { + return setHeader(name, (Object) value); + } + + /** + * Set uni-value header for the request + * + * @param name header name + * @param value header value to set + * @return {@code this} + */ + public T setHeader(CharSequence name, Object value) { + headers.set(name, value); + return asDerivedType(); + } + + /** + * Set multi-values header for the request + * + * @param name header name + * @param values {@code Iterable} with multiple header values to set + * @return {@code this} + */ + public T setHeader(CharSequence name, Iterable values) { + headers.set(name, values); + return asDerivedType(); + } + + /** + * @param name header name + * @param value header value to add + * @return {@code this} + * @see #addHeader(CharSequence, Object) + */ + public T addHeader(CharSequence name, String value) { + return addHeader(name, (Object) value); + } + + /** + * 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 + * @param value header value to add + * @return {@code this} + */ + public T addHeader(CharSequence name, Object value) { + if (value == null) { + LOGGER.warn("Value was null, set to \"\""); + value = ""; + } + + headers.add(name, value); + return asDerivedType(); + } + + /** + * 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 + * @param values {@code Iterable} with multiple header values to add + * @return {@code} + */ + public T addHeader(CharSequence name, Iterable values) { + headers.add(name, values); + return asDerivedType(); + } + + public T setHeaders(HttpHeaders headers) { + if (headers == null) { + this.headers.clear(); + } else { + this.headers = headers; + } + return asDerivedType(); + } + + /** + * Set request headers using a map {@code headers} of pair (Header name, Header values) + * 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} + */ + public T setHeaders(Map> headers) { + clearHeaders(); + if (headers != null) { + headers.forEach((name, values) -> this.headers.add(name, values)); + } + return asDerivedType(); + } + + /** + * Set single-value request headers using a map {@code headers} of pairs (Header name, Header value). + * To set headers with multiple values use {@link #setHeaders(Map)} + * + * @param headers map of header names as the map keys and header values as the map values + * @return {@code this} + */ + public T setSingleHeaders(Map headers) { + clearHeaders(); + if (headers != null) { + headers.forEach((name, value) -> this.headers.add(name, value)); + } + return asDerivedType(); + } + + @EnsuresNonNull("cookies") + private void lazyInitCookies() { + if (cookies == null) { + cookies = new ArrayList<>(3); + } + } + + public T setCookies(Collection cookies) { + this.cookies = new ArrayList<>(cookies); + return asDerivedType(); + } + + public T addCookie(Cookie cookie) { + lazyInitCookies(); + cookies.add(cookie); + return asDerivedType(); + } + + /** + * Add/replace a cookie based on its name + * + * @param cookie the new 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; + lazyInitCookies(); + for (Cookie c : cookies) { + if (c.name().equals(cookieKey)) { + replace = true; + break; + } + + index++; + } + if (!replace) { + cookies.add(cookie); + } else if (allowReplace) { + cookies.set(index, cookie); + } + return asDerivedType(); + } + + public void resetCookies() { + if (cookies != null) { + cookies.clear(); + } + } + + public void resetQuery() { + queryParams = null; + if (uri != null) { + uri = uri.withNewQuery(null); + } + } + + public void resetFormParams() { + formParams = null; + } + + public void resetNonMultipartData() { + byteData = null; + compositeByteData = null; + byteBufferData = null; + byteBufData = null; + stringData = null; + streamData = null; + bodyGenerator = null; + } + + public void resetMultipartData() { + bodyParts = null; + } + + public T setBody(File file) { + this.file = file; + return asDerivedType(); + } + + private void resetBody() { + resetFormParams(); + resetNonMultipartData(); + resetMultipartData(); + } + + public T setBody(byte[] data) { + resetBody(); + byteData = data; + return asDerivedType(); + } + + public T setBody(List data) { + resetBody(); + compositeByteData = data; + return asDerivedType(); + } + + public T setBody(String data) { + resetBody(); + stringData = data; + return asDerivedType(); + } + + public T setBody(ByteBuffer data) { + resetBody(); + byteBufferData = data; + return asDerivedType(); + } + + public T setBody(ByteBuf data) { + resetBody(); + byteBufData = data; + return asDerivedType(); + } + + public T setBody(InputStream stream) { + resetBody(); + streamData = stream; + return asDerivedType(); + } + + public T setBody(BodyGenerator bodyGenerator) { + this.bodyGenerator = bodyGenerator; + return asDerivedType(); + } + + @EnsuresNonNull("queryParams") + public T addQueryParam(String name, String value) { + if (queryParams == null) { + queryParams = new ArrayList<>(1); + } + queryParams.add(new Param(name, value)); + return asDerivedType(); + } + + @EnsuresNonNull("queryParams") + public T addQueryParams(List params) { + if (queryParams == null) { + queryParams = params; + } else { + queryParams.addAll(params); + } + return asDerivedType(); + } + + public T setQueryParams(Map> map) { + return setQueryParams(Param.map2ParamList(map)); + } + + public T setQueryParams(@Nullable List params) { + // reset existing query + if (uri != null && isNonEmpty(uri.getQuery())) { + uri = uri.withNewQuery(null); + } + queryParams = params; + return asDerivedType(); + } + + @EnsuresNonNull("formParams") + public T addFormParam(String name, String value) { + resetNonMultipartData(); + resetMultipartData(); + if (formParams == null) { + formParams = new ArrayList<>(1); + } + formParams.add(new Param(name, value)); + return asDerivedType(); + } + + public T setFormParams(Map> map) { + return setFormParams(Param.map2ParamList(map)); + } + + public T setFormParams(@Nullable List params) { + resetNonMultipartData(); + resetMultipartData(); + formParams = params; + return asDerivedType(); + } + + @EnsuresNonNull("bodyParts") + public T addBodyPart(Part bodyPart) { + resetFormParams(); + resetNonMultipartData(); + if (bodyParts == null) { + bodyParts = new ArrayList<>(); + } + bodyParts.add(bodyPart); + return asDerivedType(); + } + + @EnsuresNonNull("bodyParts") + public T setBodyParts(List bodyParts) { + this.bodyParts = new ArrayList<>(bodyParts); + return asDerivedType(); + } + + public T setProxyServer(ProxyServer proxyServer) { + this.proxyServer = proxyServer; + return asDerivedType(); + } + + public T setProxyServer(ProxyServer.Builder proxyServerBuilder) { + proxyServer = proxyServerBuilder.build(); + return asDerivedType(); + } + + public T setRealm(Realm.Builder realm) { + this.realm = realm.build(); + return asDerivedType(); + } + + public T setRealm(Realm realm) { + this.realm = realm; + return asDerivedType(); + } + + public T setFollowRedirect(boolean followRedirect) { + this.followRedirect = followRedirect; + return asDerivedType(); + } + + public T setRequestTimeout(Duration requestTimeout) { + this.requestTimeout = requestTimeout; + return asDerivedType(); + } + + public T setReadTimeout(Duration readTimeout) { + this.readTimeout = readTimeout; + return asDerivedType(); + } + + public T setRangeOffset(long rangeOffset) { + this.rangeOffset = rangeOffset; + return asDerivedType(); + } + + public T setMethod(String method) { + this.method = method; + return asDerivedType(); + } + + public T setCharset(Charset charset) { + this.charset = charset; + return asDerivedType(); + } + + public T setChannelPoolPartitioning(ChannelPoolPartitioning channelPoolPartitioning) { + this.channelPoolPartitioning = channelPoolPartitioning; + return asDerivedType(); + } + + public T setNameResolver(NameResolver nameResolver) { + this.nameResolver = nameResolver; + return asDerivedType(); + } + + public T setSignatureCalculator(@Nullable SignatureCalculator signatureCalculator) { + this.signatureCalculator = signatureCalculator; + return asDerivedType(); + } + + private RequestBuilderBase executeSignatureCalculator() { + if (signatureCalculator == null) { + return this; + } + + // 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 + // original RequestBuilder + // call setFormParams first as it resets other fields + if (formParams != null) { + rb.setFormParams(formParams); + } + if (headers != null) { + rb.headers.add(headers); + } + if (cookies != null) { + rb.setCookies(cookies); + } + if (bodyParts != null) { + rb.setBodyParts(bodyParts); + } + + // copy all other fields + // but rb.signatureCalculator, that's the whole point here + rb.uriEncoder = uriEncoder; + rb.queryParams = queryParams; + rb.uri = uri; + rb.address = address; + rb.localAddress = localAddress; + rb.byteData = byteData; + rb.compositeByteData = compositeByteData; + rb.stringData = stringData; + rb.byteBufferData = byteBufferData; + rb.byteBufData = byteBufData; + rb.streamData = streamData; + rb.bodyGenerator = bodyGenerator; + rb.virtualHost = virtualHost; + rb.proxyServer = proxyServer; + rb.realm = realm; + rb.file = file; + rb.followRedirect = followRedirect; + rb.requestTimeout = requestTimeout; + rb.rangeOffset = rangeOffset; + rb.charset = charset; + rb.channelPoolPartitioning = channelPoolPartitioning; + rb.nameResolver = nameResolver; + Request unsignedRequest = rb.build(); + signatureCalculator.calculateAndAddSignature(unsignedRequest, rb); + return rb; + } + + @EnsuresNonNull("charset") + private void updateCharset() { + String contentTypeHeader = headers.get(CONTENT_TYPE); + Charset contentTypeCharset = extractContentTypeCharsetAttribute(contentTypeHeader); + charset = withDefault(contentTypeCharset, withDefault(charset, UTF_8)); + if (contentTypeHeader != null && contentTypeHeader.regionMatches(true, 0, "text/", 0, 5) && contentTypeCharset == null) { + // add explicit charset to content-type header + headers.set(CONTENT_TYPE, contentTypeHeader + "; charset=" + charset.name()); + } + } + + private Uri computeUri() { + + Uri tempUri = uri; + if (tempUri == null) { + LOGGER.debug("setUrl hasn't been invoked. Using {}", DEFAULT_REQUEST_URL); + tempUri = DEFAULT_REQUEST_URL; + } else { + Uri.validateSupportedScheme(tempUri); + } + + return uriEncoder.encode(tempUri, queryParams); + } + + public Request build() { + updateCharset(); + RequestBuilderBase rb = executeSignatureCalculator(); + Uri finalUri = rb.computeUri(); + + // make copies of mutable internal collections + List cookiesCopy = rb.cookies == null ? Collections.emptyList() : new ArrayList<>(rb.cookies); + List formParamsCopy = rb.formParams == null ? Collections.emptyList() : new ArrayList<>(rb.formParams); + List bodyPartsCopy = rb.bodyParts == null ? Collections.emptyList() : new ArrayList<>(rb.bodyParts); + + return new DefaultRequest(rb.method, + finalUri, + rb.address, + rb.localAddress, + rb.headers, + cookiesCopy, + rb.byteData, + rb.compositeByteData, + rb.stringData, + rb.byteBufferData, + rb.byteBufData, + rb.streamData, + rb.bodyGenerator, + formParamsCopy, + bodyPartsCopy, + rb.virtualHost, + rb.proxyServer, + rb.realm, + rb.file, + rb.followRedirect, + rb.requestTimeout, + rb.readTimeout, + rb.rangeOffset, + rb.charset, + rb.channelPoolPartitioning, + rb.nameResolver); + } +} diff --git a/client/src/main/java/org/asynchttpclient/Response.java b/client/src/main/java/org/asynchttpclient/Response.java new file mode 100644 index 0000000000..220d989b09 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/Response.java @@ -0,0 +1,226 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.cookie.Cookie; +import org.asynchttpclient.netty.NettyResponse; +import org.asynchttpclient.uri.Uri; +import org.jetbrains.annotations.Nullable; + +import java.io.InputStream; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents the asynchronous HTTP response callback for an {@link AsyncCompletionHandler} + */ +public interface Response { + /** + * Returns the status code for the request. + * + * @return The status code + */ + int getStatusCode(); + + /** + * Returns the status text for the request. + * + * @return The status text + */ + String getStatusText(); + + /** + * Return the entire response body as a byte[]. + * + * @return the entire response body as a byte[]. + */ + byte[] getResponseBodyAsBytes(); + + /** + * Return the entire response body as a ByteBuffer. + * + * @return the entire response body as a ByteBuffer. + */ + 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. + * + * @return The input stream + */ + InputStream getResponseBodyAsStream(); + + /** + * Return the entire response body as a String. + * + * @param charset the charset to use when decoding the stream + * @return the entire response body as a String. + */ + String getResponseBody(Charset charset); + + /** + * Return the entire response body as a String. + * + * @return the entire response body as a String. + */ + String getResponseBody(); + + /** + * Return the request {@link Uri}. Note that if the request got redirected, the value of the {@link Uri} will be the last valid redirect url. + * + * @return the request {@link Uri}. + */ + Uri getUri(); + + /** + * Return the content-type header value. + * + * @return the content-type header value. + */ + String getContentType(); + + /** + * @param name the header name + * @return the first response header value + */ + String getHeader(CharSequence name); + + /** + * Return a {@link List} of the response header value. + * + * @param name the header name + * @return the response header value + */ + List getHeaders(CharSequence name); + + HttpHeaders getHeaders(); + + /** + * Return true if the response redirects to another object. + * + * @return True if the response redirects to another object. + */ + boolean isRedirected(); + + /** + * Subclasses SHOULD implement toString() in a way that identifies the response for logging. + * + * @return the textual representation + */ + @Override + String toString(); + + /** + * @return the list of {@link Cookie}. + */ + List getCookies(); + + /** + * Return true if the response's status has been computed by an {@link AsyncHandler} + * + * @return true if the response's status has been computed by an {@link AsyncHandler} + */ + boolean hasResponseStatus(); + + /** + * Return true if the response's headers has been computed by an {@link AsyncHandler} It will return false if the either + * {@link AsyncHandler#onStatusReceived(HttpResponseStatus)} or {@link AsyncHandler#onHeadersReceived(HttpHeaders)} returned {@link AsyncHandler.State#ABORT} + * + * @return true if the response's headers has been computed by an {@link AsyncHandler} + */ + boolean hasResponseHeaders(); + + /** + * Return true if the response's body has been computed by an {@link AsyncHandler}. + * It will return false if: + *
    + *
  • either the {@link AsyncHandler#onStatusReceived(HttpResponseStatus)} returned {@link AsyncHandler.State#ABORT}
  • + *
  • or {@link AsyncHandler#onHeadersReceived(HttpHeaders)} returned {@link AsyncHandler.State#ABORT}
  • + *
  • response body was empty
  • + *
+ * + * @return true if the response's body has been computed by an {@link AsyncHandler} to new empty bytes + */ + boolean hasResponseBody(); + + /** + * Get the remote address that the client initiated the request to. + * + * @return The remote address that the client initiated the request to. May be {@code null} if asynchronous provider is unable to provide the remote address + */ + SocketAddress getRemoteAddress(); + + /** + * Get the local address that the client initiated the request from. + * + * @return The local address that the client initiated the request from. May be {@code null} if asynchronous provider is unable to provide the local address + */ + SocketAddress getLocalAddress(); + + class ResponseBuilder { + private final List bodyParts = new ArrayList<>(1); + private @Nullable HttpResponseStatus status; + private @Nullable HttpHeaders headers; + + public void accumulate(HttpResponseStatus status) { + this.status = status; + } + + public void accumulate(HttpHeaders headers) { + this.headers = this.headers == null ? headers : this.headers.add(headers); + } + + /** + * @param bodyPart a body part (possibly empty, but will be filtered out) + */ + public void accumulate(HttpResponseBodyPart bodyPart) { + if (bodyPart.length() > 0) { + bodyParts.add(bodyPart); + } + } + + /** + * Build a {@link Response} instance + * + * @return a {@link Response} instance + */ + public @Nullable Response build() { + return status == null ? null : new NettyResponse(status, headers, bodyParts); + } + + /** + * Reset the internal state of this builder. + */ + public void reset() { + bodyParts.clear(); + status = null; + headers = null; + } + } +} diff --git a/api/src/main/java/org/asynchttpclient/SignatureCalculator.java b/client/src/main/java/org/asynchttpclient/SignatureCalculator.java similarity index 88% rename from api/src/main/java/org/asynchttpclient/SignatureCalculator.java rename to client/src/main/java/org/asynchttpclient/SignatureCalculator.java index 90339d8c59..98000f0d28 100644 --- a/api/src/main/java/org/asynchttpclient/SignatureCalculator.java +++ b/client/src/main/java/org/asynchttpclient/SignatureCalculator.java @@ -1,7 +1,7 @@ /* * Copyright 2010 Ning, Inc. * - * Ning licenses this file to you under the Apache License, version 2.0 + * This program is licensed 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: * @@ -15,6 +15,7 @@ */ package org.asynchttpclient; + /** * Interface that allows injecting signature calculator into * {@link RequestBuilder} so that signature calculation and inclusion can @@ -22,7 +23,9 @@ * * @since 1.1 */ +@FunctionalInterface public interface SignatureCalculator { + /** * Method called when {@link RequestBuilder#build} method is called. * Should first calculate signature information and then modify request @@ -35,6 +38,5 @@ public interface SignatureCalculator { * @param request Request that is being built; needed to access content to * be signed */ - void calculateAndAddSignature(Request request, - RequestBuilderBase requestBuilder); + void calculateAndAddSignature(Request request, RequestBuilderBase requestBuilder); } diff --git a/client/src/main/java/org/asynchttpclient/SslEngineFactory.java b/client/src/main/java/org/asynchttpclient/SslEngineFactory.java new file mode 100644 index 0000000000..15ec9748e4 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/SslEngineFactory.java @@ -0,0 +1,52 @@ +/* + * 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. + * 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 javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; + +@FunctionalInterface +public interface SslEngineFactory { + + /** + * Creates a new {@link SSLEngine}. + * + * @param config the client config + * @param peerHost the peer hostname + * @param peerPort the peer port + * @return new engine + */ + SSLEngine newSslEngine(AsyncHttpClientConfig config, String peerHost, int peerPort); + + /** + * Perform any necessary one-time configuration. This will be called just once before {@code newSslEngine} is called + * for the first time. + * + * @param config the client config + * @throws SSLException if initialization fails. If an exception is thrown, the instance will not be used as client + * creation will fail. + */ + default void init(AsyncHttpClientConfig config) throws SSLException { + // no op + } + + /** + * Perform any necessary cleanup. + */ + default void destroy() { + // no op + } +} diff --git a/client/src/main/java/org/asynchttpclient/channel/ChannelPool.java b/client/src/main/java/org/asynchttpclient/channel/ChannelPool.java new file mode 100755 index 0000000000..4f2bc3b9b9 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/channel/ChannelPool.java @@ -0,0 +1,78 @@ +/* + * 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. + * 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.channel; + +import io.netty.channel.Channel; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.function.Predicate; + +public interface ChannelPool { + + /** + * Add a channel to the pool + * + * @param channel an I/O channel + * @param partitionKey a key used to retrieve the cached channel + * @return true if added. + */ + boolean offer(Channel channel, Object partitionKey); + + /** + * Remove the channel associated with the uri. + * + * @param partitionKey the partition used when invoking offer + * @return the channel associated with the uri + */ + @Nullable + Channel poll(Object partitionKey); + + /** + * Remove all channels from the cache. A channel might have been associated + * with several uri. + * + * @param channel a channel + * @return the true if the channel has been removed + */ + boolean removeAll(Channel channel); + + /** + * 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)} + * + * @return true if a channel can be cached. + */ + boolean isOpen(); + + /** + * Destroy all channels that has been cached by this instance. + */ + void destroy(); + + /** + * Flush partitions based on a predicate + * + * @param predicate the predicate + */ + void flushPartitions(Predicate predicate); + + /** + * @return The number of idle channels per host. + */ + Map getIdleChannelCountPerHost(); +} diff --git a/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java b/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java new file mode 100644 index 0000000000..324a4ce343 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/channel/ChannelPoolPartitioning.java @@ -0,0 +1,117 @@ +/* + * 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. + * 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.channel; + +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.proxy.ProxyType; +import org.asynchttpclient.uri.Uri; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +@FunctionalInterface +public interface ChannelPoolPartitioning { + + Object getPartitionKey(Uri uri, String virtualHost, ProxyServer proxyServer); + + enum PerHostChannelPoolPartitioning implements ChannelPoolPartitioning { + + INSTANCE; + + @Override + public Object getPartitionKey(Uri uri, @Nullable String virtualHost, @Nullable ProxyServer proxyServer) { + String targetHostBaseUrl = uri.getBaseUrl(); + if (proxyServer == null) { + if (virtualHost == null) { + return targetHostBaseUrl; + } else { + return new CompositePartitionKey( + targetHostBaseUrl, + virtualHost, + null, + 0, + null); + } + } else { + return new CompositePartitionKey( + targetHostBaseUrl, + virtualHost, + proxyServer.getHost(), + uri.isSecured() && proxyServer.getProxyType() == ProxyType.HTTP ? + proxyServer.getSecuredPort() : + proxyServer.getPort(), + proxyServer.getProxyType()); + } + } + } + + class CompositePartitionKey { + private final String targetHostBaseUrl; + private final @Nullable String virtualHost; + private final @Nullable String proxyHost; + private final int proxyPort; + private final @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; + this.proxyPort = proxyPort; + this.proxyType = proxyType; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + CompositePartitionKey that = (CompositePartitionKey) o; + + if (proxyPort != that.proxyPort) { + return false; + } + if (!Objects.equals(targetHostBaseUrl, that.targetHostBaseUrl)) { + return false; + } + if (!Objects.equals(virtualHost, that.virtualHost)) { + return false; + } + if (!Objects.equals(proxyHost, that.proxyHost)) { + return false; + } + return proxyType == that.proxyType; + } + + @Override + public int hashCode() { + return Objects.hash(targetHostBaseUrl, virtualHost, proxyHost, proxyPort, proxyType); + } + + @Override + public String toString() { + return "CompositePartitionKey(" + + "targetHostBaseUrl=" + targetHostBaseUrl + + ", virtualHost=" + virtualHost + + ", proxyHost=" + proxyHost + + ", proxyPort=" + proxyPort + + ", proxyType=" + proxyType; + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/channel/DefaultKeepAliveStrategy.java b/client/src/main/java/org/asynchttpclient/channel/DefaultKeepAliveStrategy.java new file mode 100644 index 0000000000..f1b5cc9516 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/channel/DefaultKeepAliveStrategy.java @@ -0,0 +1,42 @@ +/* + * 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.channel; + +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpUtil; +import org.asynchttpclient.Request; + +import java.net.InetSocketAddress; + +import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE; + +/** + * Connection strategy implementing standard HTTP 1.0/1.1 behavior. + */ +public class DefaultKeepAliveStrategy implements KeepAliveStrategy { + + /** + * Implemented in accordance with RFC 7230 section 6.1 ... + */ + @Override + public boolean keepAlive(InetSocketAddress remoteAddress, Request ahcRequest, HttpRequest request, HttpResponse response) { + return HttpUtil.isKeepAlive(response) && + HttpUtil.isKeepAlive(request) && + // support non-standard Proxy-Connection + !response.headers().contains("Proxy-Connection", CLOSE, true); + } +} diff --git a/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java b/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java new file mode 100644 index 0000000000..e72cc8c13e --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/channel/KeepAliveStrategy.java @@ -0,0 +1,37 @@ +/* + * 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. + * 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.channel; + +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import org.asynchttpclient.Request; + +import java.net.InetSocketAddress; + +@FunctionalInterface +public interface KeepAliveStrategy { + + /** + * Determines whether the connection should be kept alive after this HTTP message exchange. + * + * @param remoteAddress the remote InetSocketAddress associated with the request + * @param ahcRequest the Request, as built by AHC + * @param nettyRequest the HTTP request sent to Netty + * @param nettyResponse the HTTP response received from Netty + * @return true if the connection should be kept alive, false if it should be closed. + */ + boolean keepAlive(InetSocketAddress remoteAddress, Request ahcRequest, HttpRequest nettyRequest, HttpResponse nettyResponse); +} diff --git a/client/src/main/java/org/asynchttpclient/channel/NoopChannelPool.java b/client/src/main/java/org/asynchttpclient/channel/NoopChannelPool.java new file mode 100644 index 0000000000..ae3aab81a3 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/channel/NoopChannelPool.java @@ -0,0 +1,85 @@ +/* + * 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. + * 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.channel; + +import io.netty.channel.Channel; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +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 new file mode 100644 index 0000000000..3596c67a92 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java @@ -0,0 +1,335 @@ +/* + * 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. + * 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.config; + +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.time.Duration; +import java.util.Properties; + +public final class AsyncHttpClientConfigDefaults { + + public static final String ASYNC_CLIENT_CONFIG_ROOT = "org.asynchttpclient."; + public static final String THREAD_POOL_NAME_CONFIG = "threadPoolName"; + public static final String MAX_CONNECTIONS_CONFIG = "maxConnections"; + public static final String MAX_CONNECTIONS_PER_HOST_CONFIG = "maxConnectionsPerHost"; + public static final String ACQUIRE_FREE_CHANNEL_TIMEOUT = "acquireFreeChannelTimeout"; + public static final String CONNECTION_TIMEOUT_CONFIG = "connectTimeout"; + public static final String POOLED_CONNECTION_IDLE_TIMEOUT_CONFIG = "pooledConnectionIdleTimeout"; + public static final String CONNECTION_POOL_CLEANER_PERIOD_CONFIG = "connectionPoolCleanerPeriod"; + public static final String READ_TIMEOUT_CONFIG = "readTimeout"; + public static final String REQUEST_TIMEOUT_CONFIG = "requestTimeout"; + public static final String CONNECTION_TTL_CONFIG = "connectionTtl"; + public static final String FOLLOW_REDIRECT_CONFIG = "followRedirect"; + public static final String MAX_REDIRECTS_CONFIG = "maxRedirects"; + public static final String COMPRESSION_ENFORCED_CONFIG = "compressionEnforced"; + + public static final String ENABLE_AUTOMATIC_DECOMPRESSION_CONFIG = "enableAutomaticDecompression"; + public static final String USER_AGENT_CONFIG = "userAgent"; + public static final String ENABLED_PROTOCOLS_CONFIG = "enabledProtocols"; + public static final String ENABLED_CIPHER_SUITES_CONFIG = "enabledCipherSuites"; + public static final String FILTER_INSECURE_CIPHER_SUITES_CONFIG = "filterInsecureCipherSuites"; + public static final String USE_PROXY_SELECTOR_CONFIG = "useProxySelector"; + public static final String USE_PROXY_PROPERTIES_CONFIG = "useProxyProperties"; + public static final String VALIDATE_RESPONSE_HEADERS_CONFIG = "validateResponseHeaders"; + public static final String AGGREGATE_WEBSOCKET_FRAME_FRAGMENTS_CONFIG = "aggregateWebSocketFrameFragments"; + public static final String ENABLE_WEBSOCKET_COMPRESSION_CONFIG = "enableWebSocketCompression"; + public static final String STRICT_302_HANDLING_CONFIG = "strict302Handling"; + public static final String KEEP_ALIVE_CONFIG = "keepAlive"; + public static final String MAX_REQUEST_RETRY_CONFIG = "maxRequestRetry"; + public static final String DISABLE_URL_ENCODING_FOR_BOUND_REQUESTS_CONFIG = "disableUrlEncodingForBoundRequests"; + public static final String USE_LAX_COOKIE_ENCODER_CONFIG = "useLaxCookieEncoder"; + public static final String USE_OPEN_SSL_CONFIG = "useOpenSsl"; + public static final String USE_INSECURE_TRUST_MANAGER_CONFIG = "useInsecureTrustManager"; + public static final String DISABLE_HTTPS_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG = "disableHttpsEndpointIdentificationAlgorithm"; + public static final String SSL_SESSION_CACHE_SIZE_CONFIG = "sslSessionCacheSize"; + public static final String SSL_SESSION_TIMEOUT_CONFIG = "sslSessionTimeout"; + public static final String TCP_NO_DELAY_CONFIG = "tcpNoDelay"; + public static final String SO_REUSE_ADDRESS_CONFIG = "soReuseAddress"; + public static final String SO_KEEP_ALIVE_CONFIG = "soKeepAlive"; + public static final String SO_LINGER_CONFIG = "soLinger"; + public static final String SO_SND_BUF_CONFIG = "soSndBuf"; + public static final String SO_RCV_BUF_CONFIG = "soRcvBuf"; + public static final String HTTP_CLIENT_CODEC_MAX_INITIAL_LINE_LENGTH_CONFIG = "httpClientCodecMaxInitialLineLength"; + public static final String HTTP_CLIENT_CODEC_MAX_HEADER_SIZE_CONFIG = "httpClientCodecMaxHeaderSize"; + public static final String HTTP_CLIENT_CODEC_MAX_CHUNK_SIZE_CONFIG = "httpClientCodecMaxChunkSize"; + public static final String HTTP_CLIENT_CODEC_INITIAL_BUFFER_SIZE_CONFIG = "httpClientCodecInitialBufferSize"; + public static final String DISABLE_ZERO_COPY_CONFIG = "disableZeroCopy"; + public static final String HANDSHAKE_TIMEOUT_CONFIG = "handshakeTimeout"; + public static final String CHUNKED_FILE_CHUNK_SIZE_CONFIG = "chunkedFileChunkSize"; + public static final String WEBSOCKET_MAX_BUFFER_SIZE_CONFIG = "webSocketMaxBufferSize"; + public static final String WEBSOCKET_MAX_FRAME_SIZE_CONFIG = "webSocketMaxFrameSize"; + public static final String KEEP_ENCODING_HEADER_CONFIG = "keepEncodingHeader"; + public static final String SHUTDOWN_QUIET_PERIOD_CONFIG = "shutdownQuietPeriod"; + public static final String SHUTDOWN_TIMEOUT_CONFIG = "shutdownTimeout"; + public static final String USE_NATIVE_TRANSPORT_CONFIG = "useNativeTransport"; + public static final String USE_ONLY_EPOLL_NATIVE_TRANSPORT = "useOnlyEpollNativeTransport"; + public static final String IO_THREADS_COUNT_CONFIG = "ioThreadsCount"; + public static final String HASHED_WHEEL_TIMER_TICK_DURATION = "hashedWheelTimerTickDuration"; + public static final String HASHED_WHEEL_TIMER_SIZE = "hashedWheelTimerSize"; + public static final String EXPIRED_COOKIE_EVICTION_DELAY = "expiredCookieEvictionDelay"; + + public static final String AHC_VERSION; + + static { + try (InputStream is = AsyncHttpClientConfigDefaults.class.getResourceAsStream("ahc-version.properties")) { + Properties prop = new Properties(); + prop.load(is); + AHC_VERSION = prop.getProperty("ahc.version", "UNKNOWN"); + } catch (IOException e) { + throw new ExceptionInInitializerError(e); + } + } + + private AsyncHttpClientConfigDefaults() { + } + + public static String defaultThreadPoolName() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getString(ASYNC_CLIENT_CONFIG_ROOT + THREAD_POOL_NAME_CONFIG); + } + + public static int defaultMaxConnections() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + MAX_CONNECTIONS_CONFIG); + } + + public static int defaultMaxConnectionsPerHost() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + MAX_CONNECTIONS_PER_HOST_CONFIG); + } + + public static int defaultAcquireFreeChannelTimeout() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + ACQUIRE_FREE_CHANNEL_TIMEOUT); + } + + public static Duration defaultConnectTimeout() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getDuration(ASYNC_CLIENT_CONFIG_ROOT + CONNECTION_TIMEOUT_CONFIG); + } + + public static Duration defaultPooledConnectionIdleTimeout() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getDuration(ASYNC_CLIENT_CONFIG_ROOT + POOLED_CONNECTION_IDLE_TIMEOUT_CONFIG); + } + + public static Duration defaultConnectionPoolCleanerPeriod() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getDuration(ASYNC_CLIENT_CONFIG_ROOT + CONNECTION_POOL_CLEANER_PERIOD_CONFIG); + } + + public static Duration defaultReadTimeout() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getDuration(ASYNC_CLIENT_CONFIG_ROOT + READ_TIMEOUT_CONFIG); + } + + public static Duration defaultRequestTimeout() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getDuration(ASYNC_CLIENT_CONFIG_ROOT + REQUEST_TIMEOUT_CONFIG); + } + + public static Duration defaultConnectionTtl() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getDuration(ASYNC_CLIENT_CONFIG_ROOT + CONNECTION_TTL_CONFIG); + } + + public static boolean defaultFollowRedirect() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + FOLLOW_REDIRECT_CONFIG); + } + + public static int defaultMaxRedirects() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + MAX_REDIRECTS_CONFIG); + } + + public static boolean defaultCompressionEnforced() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + COMPRESSION_ENFORCED_CONFIG); + } + + public static boolean defaultEnableAutomaticDecompression() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + ENABLE_AUTOMATIC_DECOMPRESSION_CONFIG); + } + + public static String defaultUserAgent() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getString(ASYNC_CLIENT_CONFIG_ROOT + USER_AGENT_CONFIG); + } + + public static @Nullable String[] defaultEnabledProtocols() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getStringArray(ASYNC_CLIENT_CONFIG_ROOT + ENABLED_PROTOCOLS_CONFIG); + } + + public static @Nullable String[] defaultEnabledCipherSuites() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getStringArray(ASYNC_CLIENT_CONFIG_ROOT + ENABLED_CIPHER_SUITES_CONFIG); + } + + public static boolean defaultFilterInsecureCipherSuites() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + FILTER_INSECURE_CIPHER_SUITES_CONFIG); + } + + public static boolean defaultUseProxySelector() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + USE_PROXY_SELECTOR_CONFIG); + } + + public static boolean defaultUseProxyProperties() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + USE_PROXY_PROPERTIES_CONFIG); + } + + public static boolean defaultValidateResponseHeaders() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + VALIDATE_RESPONSE_HEADERS_CONFIG); + } + + public static boolean defaultAggregateWebSocketFrameFragments() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + AGGREGATE_WEBSOCKET_FRAME_FRAGMENTS_CONFIG); + } + + public static boolean defaultEnableWebSocketCompression() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + ENABLE_WEBSOCKET_COMPRESSION_CONFIG); + } + + public static boolean defaultStrict302Handling() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + STRICT_302_HANDLING_CONFIG); + } + + public static boolean defaultKeepAlive() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + KEEP_ALIVE_CONFIG); + } + + public static int defaultMaxRequestRetry() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + MAX_REQUEST_RETRY_CONFIG); + } + + public static boolean defaultDisableUrlEncodingForBoundRequests() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + DISABLE_URL_ENCODING_FOR_BOUND_REQUESTS_CONFIG); + } + + public static boolean defaultUseLaxCookieEncoder() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + USE_LAX_COOKIE_ENCODER_CONFIG); + } + + public static boolean defaultUseOpenSsl() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + USE_OPEN_SSL_CONFIG); + } + + public static boolean defaultUseInsecureTrustManager() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + USE_INSECURE_TRUST_MANAGER_CONFIG); + } + + public static boolean defaultDisableHttpsEndpointIdentificationAlgorithm() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + DISABLE_HTTPS_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG); + } + + public static int defaultSslSessionCacheSize() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + SSL_SESSION_CACHE_SIZE_CONFIG); + } + + public static int defaultSslSessionTimeout() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + SSL_SESSION_TIMEOUT_CONFIG); + } + + public static boolean defaultTcpNoDelay() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + TCP_NO_DELAY_CONFIG); + } + + public static boolean defaultSoReuseAddress() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + SO_REUSE_ADDRESS_CONFIG); + } + + public static boolean defaultSoKeepAlive() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + SO_KEEP_ALIVE_CONFIG); + } + + public static int defaultSoLinger() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + SO_LINGER_CONFIG); + } + + public static int defaultSoSndBuf() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + SO_SND_BUF_CONFIG); + } + + public static int defaultSoRcvBuf() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + SO_RCV_BUF_CONFIG); + } + + public static int defaultHttpClientCodecMaxInitialLineLength() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + HTTP_CLIENT_CODEC_MAX_INITIAL_LINE_LENGTH_CONFIG); + } + + public static int defaultHttpClientCodecMaxHeaderSize() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + HTTP_CLIENT_CODEC_MAX_HEADER_SIZE_CONFIG); + } + + public static int defaultHttpClientCodecMaxChunkSize() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + HTTP_CLIENT_CODEC_MAX_CHUNK_SIZE_CONFIG); + } + + public static int defaultHttpClientCodecInitialBufferSize() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + HTTP_CLIENT_CODEC_INITIAL_BUFFER_SIZE_CONFIG); + } + + public static boolean defaultDisableZeroCopy() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + DISABLE_ZERO_COPY_CONFIG); + } + + public static int defaultHandshakeTimeout() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + HANDSHAKE_TIMEOUT_CONFIG); + } + + public static int defaultChunkedFileChunkSize() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + CHUNKED_FILE_CHUNK_SIZE_CONFIG); + } + + public static int defaultWebSocketMaxBufferSize() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + WEBSOCKET_MAX_BUFFER_SIZE_CONFIG); + } + + public static int defaultWebSocketMaxFrameSize() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + WEBSOCKET_MAX_FRAME_SIZE_CONFIG); + } + + public static boolean defaultKeepEncodingHeader() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + KEEP_ENCODING_HEADER_CONFIG); + } + + public static Duration defaultShutdownQuietPeriod() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getDuration(ASYNC_CLIENT_CONFIG_ROOT + SHUTDOWN_QUIET_PERIOD_CONFIG); + } + + public static Duration defaultShutdownTimeout() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getDuration(ASYNC_CLIENT_CONFIG_ROOT + SHUTDOWN_TIMEOUT_CONFIG); + } + + public static boolean defaultUseNativeTransport() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + USE_NATIVE_TRANSPORT_CONFIG); + } + + public static boolean defaultUseOnlyEpollNativeTransport() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + USE_ONLY_EPOLL_NATIVE_TRANSPORT); + } + + public static int defaultIoThreadsCount() { + int threads = AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + IO_THREADS_COUNT_CONFIG); + + // If threads value is -1 then we will automatically pick number of available processors. + if (threads == -1) { + threads = Runtime.getRuntime().availableProcessors(); + } + return threads; + } + + public static int defaultHashedWheelTimerTickDuration() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + HASHED_WHEEL_TIMER_TICK_DURATION); + } + + public static int defaultHashedWheelTimerSize() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + HASHED_WHEEL_TIMER_SIZE); + } + + public static int defaultExpiredCookieEvictionDelay() { + return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + EXPIRED_COOKIE_EVICTION_DELAY); + } +} diff --git a/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigHelper.java b/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigHelper.java new file mode 100644 index 0000000000..7bb87afb35 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigHelper.java @@ -0,0 +1,135 @@ +/* + * 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.config; + +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.time.Duration; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +public final class AsyncHttpClientConfigHelper { + + @Nullable + private static volatile Config config; + + private AsyncHttpClientConfigHelper() { + } + + public static Config getAsyncHttpClientConfig() { + if (config == null) { + config = new Config(); + } + + return config; + } + + /** + * This method invalidates the property caches. So if a system property has been changed and the effect of this change is to be seen then call reloadProperties() and then + * getAsyncHttpClientConfig() to get the new property values. + */ + public static void reloadProperties() { + final Config localInstance = config; + if (localInstance != null) { + localInstance.reload(); + } + } + + public static class Config { + + public static final String DEFAULT_AHC_PROPERTIES = "ahc-default.properties"; + public static final String CUSTOM_AHC_PROPERTIES = "ahc.properties"; + + private final ConcurrentHashMap propsCache = new ConcurrentHashMap<>(); + private final Properties defaultProperties = parsePropertiesFile(DEFAULT_AHC_PROPERTIES, true); + private volatile Properties customProperties = parsePropertiesFile(CUSTOM_AHC_PROPERTIES, false); + + public void reload() { + customProperties = parsePropertiesFile(CUSTOM_AHC_PROPERTIES, false); + 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(); + + 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); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + return props; + } + + public String getString(String key) { + return propsCache.computeIfAbsent(key, k -> { + String value = System.getProperty(k); + if (value == null) { + value = customProperties.getProperty(k); + } + if (value == null) { + value = defaultProperties.getProperty(k); + } + return value; + }); + } + + @Nullable + public String[] getStringArray(String key) { + String s = getString(key); + s = s.trim(); + if (s.isEmpty()) { + return null; + } + String[] rawArray = s.split(","); + String[] array = new String[rawArray.length]; + for (int i = 0; i < rawArray.length; i++) { + array[i] = rawArray[i].trim(); + } + return array; + } + + public int getInt(String key) { + return Integer.parseInt(getString(key)); + } + + public boolean getBoolean(String key) { + return Boolean.parseBoolean(getString(key)); + } + + public Duration getDuration(String key) { + return Duration.parse(getString(key)); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/cookie/CookieEvictionTask.java b/client/src/main/java/org/asynchttpclient/cookie/CookieEvictionTask.java new file mode 100644 index 0000000000..e2141d9ef7 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/cookie/CookieEvictionTask.java @@ -0,0 +1,44 @@ +/* + * 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.cookie; + +import io.netty.util.Timeout; +import io.netty.util.TimerTask; +import org.asynchttpclient.AsyncHttpClientConfig; + +import java.util.concurrent.TimeUnit; + +/** + * Evicts expired cookies from the {@linkplain CookieStore} periodically. + * The default delay is 30 seconds. You may override the default using + * {@linkplain AsyncHttpClientConfig#expiredCookieEvictionDelay()}. + */ +public class CookieEvictionTask implements TimerTask { + + private final long evictDelayInMs; + private final CookieStore cookieStore; + + public CookieEvictionTask(long evictDelayInMs, CookieStore cookieStore) { + this.evictDelayInMs = evictDelayInMs; + this.cookieStore = cookieStore; + } + + @Override + public void run(Timeout timeout) throws Exception { + cookieStore.evictExpired(); + timeout.timer().newTimeout(this, evictDelayInMs, TimeUnit.MILLISECONDS); + } +} diff --git a/client/src/main/java/org/asynchttpclient/cookie/CookieStore.java b/client/src/main/java/org/asynchttpclient/cookie/CookieStore.java new file mode 100644 index 0000000000..d19a0d13e9 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/cookie/CookieStore.java @@ -0,0 +1,93 @@ +/* + * 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.cookie; + +import io.netty.handler.codec.http.cookie.Cookie; +import org.asynchttpclient.uri.Uri; +import org.asynchttpclient.util.Counted; + +import java.net.CookieManager; +import java.util.List; +import java.util.function.Predicate; + +/** + * This interface represents an abstract store for {@link Cookie} objects. + * + *

{@link CookieManager} will call {@code CookieStore.add} to save cookies + * for every incoming HTTP response, and call {@code CookieStore.get} to + * retrieve cookie for every outgoing HTTP request. A CookieStore + * is responsible for removing HttpCookie instances which have expired. + * + * @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 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 a URI + * @param cookie the {@link Cookie cookie} to be added + */ + void add(Uri uri, Cookie cookie); + + /** + * Retrieve cookies associated with given URI, or whose domain matches the given URI. Only cookies that + * have not expired are returned. This is called for every outgoing HTTP request. + * + * @param uri the {@link Uri uri} associated with the cookies to be returned + * @return an immutable list of Cookie, return empty list if no cookies match the given URI + */ + List get(Uri uri); + + /** + * Get all not-expired cookies in cookie store. + * + * @return an immutable list of http cookies; + * return empty list if there's no http cookie in store + */ + List getAll(); + + /** + * Remove a cookie from store. + * + * @param predicate that indicates what cookies to remove + * @return {@code true} if this store contained the specified cookie + * @throws NullPointerException if {@code cookie} is {@code null} + */ + boolean remove(Predicate predicate); + + /** + * Remove all cookies in this cookie store. + * + * @return true if any cookies were purged. + */ + boolean clear(); + + /** + * Evicts all the cookies that expired as of the time this method is run. + */ + void evictExpired(); +} diff --git a/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java b/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java new file mode 100644 index 0000000000..5832185cc5 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/cookie/ThreadSafeCookieStore.java @@ -0,0 +1,310 @@ +/* + * 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.cookie; + +import io.netty.handler.codec.http.cookie.Cookie; +import org.asynchttpclient.uri.Uri; +import org.asynchttpclient.util.MiscUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +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<>(); + private final AtomicInteger counter = new AtomicInteger(); + + @Override + public void add(Uri uri, Cookie cookie) { + String thisRequestDomain = requestDomain(uri); + String thisRequestPath = requestPath(uri); + + add(thisRequestDomain, thisRequestPath, cookie); + } + + @Override + public List get(Uri uri) { + return get(requestDomain(uri), requestPath(uri), uri.isSecured()); + } + + @Override + public List getAll() { + return cookieJar.values() + .stream() + .flatMap(map -> map.values().stream()) + .filter(pair -> !hasCookieExpired(pair.cookie, pair.createdAt)) + .map(pair -> pair.cookie) + .collect(Collectors.toList()); + } + + @Override + public boolean remove(Predicate predicate) { + final boolean[] removed = {false}; + cookieJar.forEach((key, value) -> { + if (!removed[0]) { + removed[0] = value.entrySet().removeIf(v -> predicate.test(v.getValue().cookie)); + } + }); + if (removed[0]) { + cookieJar.entrySet().removeIf(entry -> entry.getValue() == null || entry.getValue().isEmpty()); + } + return removed[0]; + } + + @Override + public boolean clear() { + boolean result = !cookieJar.isEmpty(); + cookieJar.clear(); + return result; + } + + @Override + public void evictExpired() { + removeExpired(); + } + + @Override + public int incrementAndGet() { + return counter.incrementAndGet(); + } + + @Override + public int decrementAndGet() { + return counter.decrementAndGet(); + } + + @Override + public int count() { + return counter.get(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + public Map> getUnderlying() { + return new HashMap<>(cookieJar); + } + + private static String requestDomain(Uri requestUri) { + return requestUri.getHost().toLowerCase(); + } + + private static String requestPath(Uri requestUri) { + return requestUri.getPath().isEmpty() ? "/" : requestUri.getPath(); + } + + // rfc6265#section-5.2.3 + // Let cookie-domain be the attribute-value without the leading %x2E (".") character. + private static AbstractMap.SimpleEntry cookieDomain(@Nullable String cookieDomain, String requestDomain) { + if (cookieDomain != null) { + String normalizedCookieDomain = cookieDomain.toLowerCase(); + return new AbstractMap.SimpleEntry<>( + !cookieDomain.isEmpty() && cookieDomain.charAt(0) == '.' ? + normalizedCookieDomain.substring(1) : + normalizedCookieDomain, false); + } else { + return new AbstractMap.SimpleEntry<>(requestDomain, true); + } + } + + // rfc6265#section-5.2.4 + private static String cookiePath(@Nullable String rawCookiePath, String requestPath) { + if (MiscUtils.isNonEmpty(rawCookiePath) && rawCookiePath.charAt(0) == '/') { + return rawCookiePath; + } else { + // rfc6265#section-5.1.4 + int indexOfLastSlash = requestPath.lastIndexOf('/'); + if (!requestPath.isEmpty() && requestPath.charAt(0) == '/' && indexOfLastSlash > 0) { + return requestPath.substring(0, indexOfLastSlash); + } else { + return "/"; + } + } + } + + private static boolean hasCookieExpired(Cookie cookie, long whenCreated) { + // if not specify max-age, this cookie should be discarded when user agent is to be closed, but it is not expired. + if (cookie.maxAge() == Cookie.UNDEFINED_MAX_AGE) { + return false; + } + + if (cookie.maxAge() <= 0) { + return true; + } + + if (whenCreated > 0) { + long deltaSecond = (System.currentTimeMillis() - whenCreated) / 1000; + return deltaSecond > cookie.maxAge(); + } else { + return false; + } + } + + // rfc6265#section-5.1.4 + private static boolean pathsMatch(String cookiePath, String requestPath) { + return Objects.equals(cookiePath, requestPath) || + requestPath.startsWith(cookiePath) && (cookiePath.charAt(cookiePath.length() - 1) == '/' || requestPath.charAt(cookiePath.length()) == '/'); + } + + private void add(String requestDomain, String requestPath, Cookie cookie) { + AbstractMap.SimpleEntry pair = cookieDomain(cookie.domain(), requestDomain); + String keyDomain = pair.getKey(); + boolean hostOnly = pair.getValue(); + String keyPath = cookiePath(cookie.path(), requestPath); + CookieKey key = new CookieKey(cookie.name().toLowerCase(), keyPath); + + if (hasCookieExpired(cookie, 0)) { + cookieJar.getOrDefault(keyDomain, Collections.emptyMap()).remove(key); + } else { + final Map innerMap = cookieJar.computeIfAbsent(keyDomain, domain -> new ConcurrentHashMap<>()); + innerMap.put(key, new StoredCookie(cookie, hostOnly, cookie.maxAge() != Cookie.UNDEFINED_MAX_AGE)); + } + } + + private List get(String domain, String path, boolean secure) { + boolean exactDomainMatch = true; + String subDomain = domain; + List results = null; + + while (MiscUtils.isNonEmpty(subDomain)) { + final List storedCookies = getStoredCookies(subDomain, path, secure, exactDomainMatch); + subDomain = DomainUtils.getSubDomain(subDomain); + exactDomainMatch = false; + if (storedCookies.isEmpty()) { + continue; + } + if (results == null) { + results = new ArrayList<>(4); + } + results.addAll(storedCookies); + } + + return results == null ? Collections.emptyList() : results; + } + + private List getStoredCookies(String domain, String path, boolean secure, boolean isExactMatch) { + final Map innerMap = cookieJar.get(domain); + if (innerMap == null) { + return Collections.emptyList(); + } + + return innerMap.entrySet().stream().filter(pair -> { + CookieKey key = pair.getKey(); + StoredCookie storedCookie = pair.getValue(); + boolean hasCookieExpired = hasCookieExpired(storedCookie.cookie, storedCookie.createdAt); + return !hasCookieExpired && + (isExactMatch || !storedCookie.hostOnly) && + pathsMatch(key.path, path) && + (secure || !storedCookie.cookie.isSecure()); + }).map(v -> v.getValue().cookie).collect(Collectors.toList()); + } + + private void removeExpired() { + final boolean[] removed = {false}; + + 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()); + } + } + + private static class CookieKey implements Comparable { + final String name; + final String path; + + CookieKey(String name, String path) { + this.name = name; + this.path = path; + } + + @Override + public int compareTo(@NotNull CookieKey cookieKey) { + requireNonNull(cookieKey, "Parameter can't be null"); + + int result; + if ((result = name.compareTo(cookieKey.name)) == 0) { + result = path.compareTo(cookieKey.path); + } + return result; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof CookieKey && compareTo((CookieKey) obj) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(name, path); + } + + @Override + public String toString() { + return String.format("%s: %s", name, path); + } + } + + private static class StoredCookie { + final Cookie cookie; + final boolean hostOnly; + final boolean persistent; + final long createdAt = System.currentTimeMillis(); + + StoredCookie(Cookie cookie, boolean hostOnly, boolean persistent) { + this.cookie = cookie; + this.hostOnly = hostOnly; + this.persistent = persistent; + } + + @Override + public String toString() { + return String.format("%s; hostOnly %s; persistent %s", cookie.toString(), hostOnly, persistent); + } + } + + public static final class DomainUtils { + private static final char DOT = '.'; + + public static @Nullable String getSubDomain(@Nullable String domain) { + if (domain == null || domain.isEmpty()) { + return null; + } + final int indexOfDot = domain.indexOf(DOT); + if (indexOfDot == -1) { + return null; + } + return domain.substring(indexOfDot + 1); + } + + private DomainUtils() { + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/exception/ChannelClosedException.java b/client/src/main/java/org/asynchttpclient/exception/ChannelClosedException.java new file mode 100644 index 0000000000..4b63997aa4 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/exception/ChannelClosedException.java @@ -0,0 +1,33 @@ +/* + * 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.exception; + +import java.io.IOException; + +import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace; + +/** + * 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() { + super("Channel closed"); + } +} 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 0000000000..6e5902b9b8 --- /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 new file mode 100644 index 0000000000..d66c3b76a7 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/exception/PoolAlreadyClosedException.java @@ -0,0 +1,33 @@ +/* + * 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.exception; + +import java.io.IOException; + +import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace; + +/** + * 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() { + super("Pool is already closed"); + } +} diff --git a/client/src/main/java/org/asynchttpclient/exception/RemotelyClosedException.java b/client/src/main/java/org/asynchttpclient/exception/RemotelyClosedException.java new file mode 100644 index 0000000000..2b1977a6f9 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/exception/RemotelyClosedException.java @@ -0,0 +1,33 @@ +/* + * 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.exception; + +import java.io.IOException; + +import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace; + +/** + * 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() { + super("Remotely closed"); + } +} diff --git a/client/src/main/java/org/asynchttpclient/exception/TooManyConnectionsException.java b/client/src/main/java/org/asynchttpclient/exception/TooManyConnectionsException.java new file mode 100644 index 0000000000..6797c68c70 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/exception/TooManyConnectionsException.java @@ -0,0 +1,29 @@ +/* + * 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.exception; + +import java.io.IOException; + +/** + * 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 new file mode 100644 index 0000000000..e7959bb374 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/exception/TooManyConnectionsPerHostException.java @@ -0,0 +1,30 @@ +/* + * 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.exception; + +import java.io.IOException; + +/** + * 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/FilterContext.java b/client/src/main/java/org/asynchttpclient/filter/FilterContext.java new file mode 100644 index 0000000000..7334553894 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/filter/FilterContext.java @@ -0,0 +1,156 @@ +/* + * 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 io.netty.handler.codec.http.HttpHeaders; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.Request; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; + +/** + * A {@link FilterContext} can be used to decorate {@link Request} and {@link AsyncHandler} from a list of {@link RequestFilter}. + * {@link RequestFilter} gets executed before the HTTP request is made to the remote server. Once the response bytes are + * received, a {@link FilterContext} is then passed to the list of {@link ResponseFilter}. {@link ResponseFilter} + * gets invoked before the response gets processed, e.g. before authorization, redirection and invocation of {@link AsyncHandler} + * gets processed. + *
+ * Invoking {@link FilterContext#getResponseStatus()} returns an instance of {@link HttpResponseStatus} + * that can be used to decide if the response processing should continue or not. You can stop the current response processing + * and replay the request but creating a {@link FilterContext}. The {@link AsyncHttpClient} + * will interrupt the processing and "replay" the associated {@link Request} instance. + * + * @param the handler result type + */ +public class FilterContext { + + private final FilterContextBuilder builder; + + /** + * Create a new {@link FilterContext} + * + * @param builder a {@link FilterContextBuilder} + */ + private FilterContext(FilterContextBuilder builder) { + this.builder = builder; + } + + /** + * @return the original or decorated {@link AsyncHandler} + */ + public AsyncHandler getAsyncHandler() { + return builder.asyncHandler; + } + + /** + * @return the original or decorated {@link Request} + */ + public Request getRequest() { + return builder.request; + } + + /** + * @return the unprocessed response's {@link HttpResponseStatus} + */ + public @Nullable HttpResponseStatus getResponseStatus() { + return builder.responseStatus; + } + + /** + * @return the response {@link HttpHeaders} + */ + public @Nullable HttpHeaders getResponseHeaders() { + return builder.headers; + } + + /** + * @return true if the current response's processing needs to be interrupted and a new {@link Request} be executed. + */ + public boolean replayRequest() { + return builder.replayRequest; + } + + /** + * @return the {@link IOException} + */ + public @Nullable IOException getIOException() { + return builder.ioException; + } + + public static class FilterContextBuilder { + private AsyncHandler asyncHandler; + private Request request; + private @Nullable HttpResponseStatus responseStatus; + private boolean replayRequest; + private @Nullable IOException ioException; + private @Nullable HttpHeaders headers; + + public FilterContextBuilder(AsyncHandler asyncHandler, Request request) { + this.asyncHandler = asyncHandler; + this.request = request; + } + + public FilterContextBuilder(FilterContext clone) { + asyncHandler = clone.getAsyncHandler(); + request = clone.getRequest(); + responseStatus = clone.getResponseStatus(); + replayRequest = clone.replayRequest(); + ioException = clone.getIOException(); + } + + public AsyncHandler getAsyncHandler() { + return asyncHandler; + } + + public FilterContextBuilder asyncHandler(AsyncHandler asyncHandler) { + this.asyncHandler = asyncHandler; + return this; + } + + public Request getRequest() { + return request; + } + + public FilterContextBuilder request(Request request) { + this.request = request; + return this; + } + + public FilterContextBuilder responseStatus(HttpResponseStatus responseStatus) { + this.responseStatus = responseStatus; + return this; + } + + public FilterContextBuilder responseHeaders(HttpHeaders headers) { + this.headers = headers; + return this; + } + + public FilterContextBuilder replayRequest(boolean replayRequest) { + this.replayRequest = replayRequest; + return this; + } + + public FilterContextBuilder ioException(IOException ioException) { + this.ioException = ioException; + return this; + } + + public FilterContext build() { + return new FilterContext<>(this); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java b/client/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java new file mode 100644 index 0000000000..182ea56e29 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java @@ -0,0 +1,36 @@ +/* + * 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.AsyncHttpClient; +import org.asynchttpclient.Request; +import org.asynchttpclient.exception.FilterException; + +import java.io.IOException; + +/** + * This filter is invoked when an {@link IOException} occurs during a http transaction. + */ +public interface IOExceptionFilter { + + /** + * An {@link AsyncHttpClient} will invoke {@link IOExceptionFilter#filter} and will + * use the returned {@link FilterContext} to replay the {@link Request} or abort the processing. + * + * @param ctx a {@link FilterContext} + * @param the handler result type + * @return {@link FilterContext}. The {@link FilterContext} instance may not the same as the original one. + * @throws FilterException to interrupt the filter processing. + */ + FilterContext filter(FilterContext ctx) throws FilterException; +} diff --git a/client/src/main/java/org/asynchttpclient/filter/ReleasePermitOnComplete.java b/client/src/main/java/org/asynchttpclient/filter/ReleasePermitOnComplete.java new file mode 100644 index 0000000000..baf8585078 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/filter/ReleasePermitOnComplete.java @@ -0,0 +1,76 @@ +/* + * 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.filter; + +import org.asynchttpclient.AsyncHandler; + +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.Semaphore; + +/** + * Wrapper for {@link AsyncHandler}s to release a permit on {@link AsyncHandler#onCompleted()}. This is done via a dynamic proxy to preserve all interfaces of the wrapped handler. + */ +public final class ReleasePermitOnComplete { + + private ReleasePermitOnComplete() { + // Prevent outside initialization + } + + /** + * Wrap handler to release the permit of the semaphore on {@link AsyncHandler#onCompleted()}. + * + * @param handler the handler to be wrapped + * @param available the Semaphore to be released when the wrapped handler is completed + * @param the handler result type + * @return the wrapped handler + */ + @SuppressWarnings("unchecked") + public static AsyncHandler wrap(final AsyncHandler handler, final Semaphore available) { + Class handlerClass = handler.getClass(); + ClassLoader classLoader = handlerClass.getClassLoader(); + Class[] interfaces = allInterfaces(handlerClass); + + return (AsyncHandler) Proxy.newProxyInstance(classLoader, interfaces, (proxy, method, args) -> { + try { + return method.invoke(handler, args); + } finally { + switch (method.getName()) { + case "onCompleted": + case "onThrowable": + available.release(); + default: + } + } + }); + } + + /** + * Extract all interfaces of a class. + * + * @param handlerClass the handler class + * @return all interfaces implemented by this class + */ + private static Class[] allInterfaces(Class handlerClass) { + Set> allInterfaces = new HashSet<>(); + for (Class clazz = handlerClass; clazz != null; clazz = clazz.getSuperclass()) { + Collections.addAll(allInterfaces, clazz.getInterfaces()); + } + return allInterfaces.toArray(new Class[0]); + } +} diff --git a/api/src/main/java/org/asynchttpclient/filter/RequestFilter.java b/client/src/main/java/org/asynchttpclient/filter/RequestFilter.java similarity index 84% rename from api/src/main/java/org/asynchttpclient/filter/RequestFilter.java rename to client/src/main/java/org/asynchttpclient/filter/RequestFilter.java index 9116ed0c1f..d4eaf8a328 100644 --- a/api/src/main/java/org/asynchttpclient/filter/RequestFilter.java +++ b/client/src/main/java/org/asynchttpclient/filter/RequestFilter.java @@ -12,17 +12,21 @@ */ package org.asynchttpclient.filter; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.exception.FilterException; + /** * A Filter interface that gets invoked before making an actual request. */ public interface RequestFilter { /** - * An {@link org.asynchttpclient.AsyncHttpProvider} will invoke {@link RequestFilter#filter} and will use the + * An {@link AsyncHttpClient} will invoke {@link RequestFilter#filter} and will use the * returned {@link FilterContext#getRequest()} and {@link FilterContext#getAsyncHandler()} to continue the request * processing. * * @param ctx a {@link FilterContext} + * @param the handler result type * @return {@link FilterContext}. The {@link FilterContext} instance may not the same as the original one. * @throws FilterException to interrupt the filter processing. */ diff --git a/api/src/main/java/org/asynchttpclient/filter/ResponseFilter.java b/client/src/main/java/org/asynchttpclient/filter/ResponseFilter.java similarity index 88% rename from api/src/main/java/org/asynchttpclient/filter/ResponseFilter.java rename to client/src/main/java/org/asynchttpclient/filter/ResponseFilter.java index a49a71f948..939adf6d8a 100644 --- a/api/src/main/java/org/asynchttpclient/filter/ResponseFilter.java +++ b/client/src/main/java/org/asynchttpclient/filter/ResponseFilter.java @@ -12,6 +12,9 @@ */ 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 * before the actual response's status code get processed. That means authorization, proxy authentication and redirects @@ -20,12 +23,13 @@ public interface ResponseFilter { /** - * An {@link org.asynchttpclient.AsyncHttpProvider} will invoke {@link ResponseFilter#filter} and will use the + * An {@link AsyncHttpClient} will invoke {@link ResponseFilter#filter} and will use the * returned {@link FilterContext#replayRequest()} and {@link FilterContext#getAsyncHandler()} to decide if the response * processing can continue. If {@link FilterContext#replayRequest()} return true, a new request will be made * using {@link FilterContext#getRequest()} and the current response processing will be ignored. * * @param ctx a {@link FilterContext} + * @param the handler result type * @return {@link FilterContext}. The {@link FilterContext} instance may not the same as the original one. * @throws FilterException to interrupt the filter processing. */ diff --git a/client/src/main/java/org/asynchttpclient/filter/ThrottleRequestFilter.java b/client/src/main/java/org/asynchttpclient/filter/ThrottleRequestFilter.java new file mode 100644 index 0000000000..3d5d9943ad --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/filter/ThrottleRequestFilter.java @@ -0,0 +1,61 @@ +/* + * 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.exception.FilterException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +/** + * A {@link RequestFilter} throttles requests and block when the number of permits is reached, + * waiting for the response to arrives before executing the next request. + */ +public class ThrottleRequestFilter implements RequestFilter { + private static final Logger logger = LoggerFactory.getLogger(ThrottleRequestFilter.class); + private final Semaphore available; + private final int maxWait; + + public ThrottleRequestFilter(int maxConnections) { + this(maxConnections, Integer.MAX_VALUE); + } + + public ThrottleRequestFilter(int maxConnections, int maxWait) { + this(maxConnections, maxWait, false); + } + + public ThrottleRequestFilter(int maxConnections, int maxWait, boolean fair) { + this.maxWait = maxWait; + available = new Semaphore(maxConnections, fair); + } + + @Override + public FilterContext filter(FilterContext ctx) throws FilterException { + try { + if (logger.isDebugEnabled()) { + logger.debug("Current Throttling Status {}", available.availablePermits()); + } + if (!available.tryAcquire(maxWait, TimeUnit.MILLISECONDS)) { + throw new FilterException(String.format("No slot available for processing Request %s with AsyncHandler %s", ctx.getRequest(), ctx.getAsyncHandler())); + } + } catch (InterruptedException e) { + throw new FilterException(String.format("Interrupted Request %s with AsyncHandler %s", ctx.getRequest(), ctx.getAsyncHandler())); + } + + return new FilterContext.FilterContextBuilder<>(ctx) + .asyncHandler(ReleasePermitOnComplete.wrap(ctx.getAsyncHandler(), available)) + .build(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/handler/BodyDeferringAsyncHandler.java b/client/src/main/java/org/asynchttpclient/handler/BodyDeferringAsyncHandler.java new file mode 100644 index 0000000000..dc58fc2c5b --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/handler/BodyDeferringAsyncHandler.java @@ -0,0 +1,298 @@ +/* + * 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.handler; + +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.jetbrains.annotations.Nullable; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; + +/** + * An AsyncHandler that returns Response (without body, so status code and + * headers only) as fast as possible for inspection, but leaves you the option + * to defer body consumption. + *
+ * This class introduces new call: getResponse(), that blocks caller thread as + * 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()}), which + * would not work for you, since a potentially large response body is about to + * 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. + *
+ * All these above means that this AsyncHandler needs a bit of different + * handling than "recommended" way. Some examples: + *
+ *

+ *     OutputStream fos = ...
+ *     BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(fos);
+ *     // client executes async
+ *     Future<Response> fr = client.prepareGet("...).execute(
+ * 	bdah);
+ *     // main thread will block here until headers are available
+ *     Response response = bdah.getResponse();
+ *     // you can continue examine headers while actual body download happens
+ *     // in separate thread
+ *     // ...
+ *     // finally "join" the download
+ *     fr.get();
+ * 
+ *
+ *
+ *     PipedOutputStream pout = new PipedOutputStream();
+ *     try (PipedInputStream pin = new PipedInputStream(pout)) {
+ *         BodyDeferringAsyncHandler handler = new BodyDeferringAsyncHandler(pout);
+ *         ListenableFuture<Response> respFut = client.prepareGet(getTargetUrl()).execute(handler);
+ *         Response resp = handler.getResponse();
+ *         // main thread will block here until headers are available
+ *         if (resp.getStatusCode() == 200) {
+ *             try (InputStream is = new BodyDeferringInputStream(respFut, handler, pin)) {
+ *                 // consume InputStream
+ *                 ...
+ *             }
+ *         } else {
+ *             // handle unexpected response status code
+ *             ...
+ *         }
+ *     }
+ * 
+ */ +public class BodyDeferringAsyncHandler implements AsyncHandler { + + private final Response.ResponseBuilder responseBuilder = new Response.ResponseBuilder(); + + private final CountDownLatch headersArrived = new CountDownLatch(1); + + private final OutputStream output; + private final Semaphore semaphore = new Semaphore(1); + private boolean responseSet; + private volatile @Nullable Response response; + private volatile @Nullable Throwable throwable; + + public BodyDeferringAsyncHandler(final OutputStream os) { + output = os; + responseSet = false; + } + + @Override + public void onThrowable(Throwable t) { + throwable = t; + // Counting down to handle error cases too. + // In "premature exceptions" cases, the onBodyPartReceived() and + // onCompleted() + // methods will never be invoked, leaving caller of getResponse() method + // blocked forever. + try { + semaphore.acquire(); + } catch (InterruptedException e) { + // Ignore + } finally { + headersArrived.countDown(); + semaphore.release(); + } + + try { + closeOut(); + } catch (IOException e) { + // ignore + } + } + + @Override + public State onStatusReceived(HttpResponseStatus responseStatus) { + responseBuilder.reset(); + responseBuilder.accumulate(responseStatus); + return State.CONTINUE; + } + + @Override + public State onHeadersReceived(HttpHeaders headers) { + responseBuilder.accumulate(headers); + return State.CONTINUE; + } + + @Override + public State onTrailingHeadersReceived(HttpHeaders headers) { + responseBuilder.accumulate(headers); + return State.CONTINUE; + } + + @Override + public void onRetry() { + throw new UnsupportedOperationException(getClass().getSimpleName() + " cannot retry a request."); + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { + // body arrived, flush headers + if (!responseSet) { + response = responseBuilder.build(); + responseSet = true; + headersArrived.countDown(); + } + + output.write(bodyPart.getBodyPartBytes()); + return State.CONTINUE; + } + + protected void closeOut() throws IOException { + try { + output.flush(); + } finally { + output.close(); + } + } + + @Override + public @Nullable Response onCompleted() throws IOException { + + if (!responseSet) { + response = responseBuilder.build(); + responseSet = true; + } + + // Counting down to handle error cases too. + // In "normal" cases, latch is already at 0 here + // But in other cases, for example when because of some error + // onBodyPartReceived() is never called, the caller + // of getResponse() would remain blocked infinitely. + // By contract, onCompleted() is always invoked, even in case of errors + headersArrived.countDown(); + + closeOut(); + + try { + semaphore.acquire(); + if (throwable != null) { + throw new IOException(throwable); + } else { + // sending out current response + return responseBuilder.build(); + } + } catch (InterruptedException e) { + return null; + } finally { + semaphore.release(); + } + } + + /** + * This method -- unlike Future<Reponse>.get() -- will block only as long, + * as headers arrive. This is useful for large transfers, to examine headers + * ASAP, and defer body streaming to it's fine destination and prevent + * unneeded bandwidth consumption. The response here will contain the very + * 1st response from server, so status code and headers, but it might be + * incomplete in case of broken servers sending trailing headers. In that + * case, the "usual" Future<Response>.get() method will return complete + * headers, but multiple invocations of getResponse() will always return the + * 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 note that this method might return {@code null} + * in case of some errors. + * + * @return a {@link Response} + * @throws InterruptedException if the latch is interrupted + * @throws IOException if the handler completed with an exception + */ + public @Nullable Response getResponse() throws InterruptedException, IOException { + // block here as long as headers arrive + headersArrived.await(); + + try { + semaphore.acquire(); + if (throwable != null) { + throw new IOException(throwable.getMessage(), throwable); + } else { + return response; + } + } finally { + semaphore.release(); + } + } + + // == + + /** + * A simple helper class that is used to perform automatic "join" for async + * download and the error checking of the Future of the request. + */ + public static class BodyDeferringInputStream extends FilterInputStream { + private final Future future; + + private final BodyDeferringAsyncHandler bdah; + + public BodyDeferringInputStream(final Future future, final BodyDeferringAsyncHandler bdah, final InputStream in) { + super(in); + this.future = future; + this.bdah = bdah; + } + + /** + * Closes the input stream, and "joins" (wait for complete execution + * together with potential exception thrown) of the async request. + */ + @Override + public void close() throws IOException { + // close + super.close(); + // "join" async request + try { + getLastResponse(); + } catch (ExecutionException e) { + throw new IOException(e.getMessage(), e.getCause()); + } catch (InterruptedException e) { + throw new IOException(e.getMessage(), e); + } + } + + /** + * Delegates to {@link BodyDeferringAsyncHandler#getResponse()}. Will + * blocks as long as headers arrives only. Might return + * {@code null}. See + * {@link BodyDeferringAsyncHandler#getResponse()} method for details. + * + * @return a {@link Response} + * @throws InterruptedException if the latch is interrupted + * @throws IOException if the handler completed with an exception + */ + public @Nullable Response getAsapResponse() throws InterruptedException, IOException { + return bdah.getResponse(); + } + + /** + * Delegates to {@code Future$lt;Response>#get()} method. Will block + * as long as complete response arrives. + * + * @return a {@link Response} + * @throws ExecutionException if the computation threw an exception + * @throws InterruptedException if the current thread was interrupted + */ + public Response getLastResponse() throws InterruptedException, ExecutionException { + return future.get(); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/handler/MaxRedirectException.java b/client/src/main/java/org/asynchttpclient/handler/MaxRedirectException.java new file mode 100644 index 0000000000..993f87d93e --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/handler/MaxRedirectException.java @@ -0,0 +1,29 @@ +/* + * 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. + * 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.handler; + +import org.asynchttpclient.DefaultAsyncHttpClientConfig; + +/** + * Thrown when the {@link DefaultAsyncHttpClientConfig#getMaxRedirects()} has been reached. + */ +public class MaxRedirectException extends Exception { + private static final long serialVersionUID = 1L; + + public MaxRedirectException(String msg) { + super(msg, null, true, false); + } +} diff --git a/client/src/main/java/org/asynchttpclient/handler/ProgressAsyncHandler.java b/client/src/main/java/org/asynchttpclient/handler/ProgressAsyncHandler.java new file mode 100644 index 0000000000..04839e2760 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/handler/ProgressAsyncHandler.java @@ -0,0 +1,53 @@ +/* + * 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.handler; + +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.Request; + +import java.io.File; +import java.io.FileInputStream; + +/** + * An extended {@link AsyncHandler} with two extra callback who get invoked during the content upload to a remote server. + * This {@link AsyncHandler} must be used only with PUT and POST request. + */ +public interface ProgressAsyncHandler extends AsyncHandler { + + /** + * 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. + */ + State onHeadersWritten(); + + /** + * 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. + */ + State onContentWritten(); + + /** + * Invoked when the I/O operation associated with the {@link Request} body wasn't fully written in a single I/O write + * operation. This method is never invoked if the write operation complete in a sinfle I/O write. + * + * @param amount The amount of bytes to transfer. + * @param current The amount of bytes transferred + * @param total The total number of bytes transferred + * @return a {@link AsyncHandler.State} telling to CONTINUE or ABORT the current processing. + */ + State onContentWriteProgress(long amount, long current, long total); +} diff --git a/client/src/main/java/org/asynchttpclient/handler/TransferCompletionHandler.java b/client/src/main/java/org/asynchttpclient/handler/TransferCompletionHandler.java new file mode 100644 index 0000000000..e0705ad25f --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/handler/TransferCompletionHandler.java @@ -0,0 +1,203 @@ +/* + * 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.handler; + +import io.netty.handler.codec.http.HttpHeaders; +import org.asynchttpclient.AsyncCompletionHandlerBase; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.Response; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * A {@link AsyncHandler} that can be used to notify a set of {@link TransferListener} + *
+ *
+ *
+ * AsyncHttpClient client = new AsyncHttpClient();
+ * TransferCompletionHandler tl = new TransferCompletionHandler();
+ * tl.addTransferListener(new TransferListener() {
+ *
+ * public void onRequestHeadersSent(HttpHeaders headers) {
+ * }
+ *
+ * public void onResponseHeadersReceived(HttpHeaders headers) {
+ * }
+ *
+ * public void onBytesReceived(ByteBuffer buffer) {
+ * }
+ *
+ * public void onBytesSent(long amount, long current, long total) {
+ * }
+ *
+ * public void onRequestResponseCompleted() {
+ * }
+ *
+ * public void onThrowable(Throwable t) {
+ * }
+ * });
+ *
+ * Response response = httpClient.prepareGet("http://.../").execute(tl).get();
+ * 
+ *
+ */ +public class TransferCompletionHandler extends AsyncCompletionHandlerBase { + private static final Logger logger = LoggerFactory.getLogger(TransferCompletionHandler.class); + + private final ConcurrentLinkedQueue listeners = new ConcurrentLinkedQueue<>(); + private final boolean accumulateResponseBytes; + private @Nullable HttpHeaders headers; + + /** + * Create a TransferCompletionHandler that will not accumulate bytes. The resulting {@link Response#getResponseBody()}, + * {@link Response#getResponseBodyAsStream()} will throw an IllegalStateException if called. + */ + public TransferCompletionHandler() { + this(false); + } + + /** + * Create a TransferCompletionHandler that can or cannot accumulate bytes and make it available when {@link Response#getResponseBody()} get called. The + * default is false. + * + * @param accumulateResponseBytes true to accumulates bytes in memory. + */ + public TransferCompletionHandler(boolean accumulateResponseBytes) { + this.accumulateResponseBytes = accumulateResponseBytes; + } + + public TransferCompletionHandler addTransferListener(TransferListener t) { + listeners.offer(t); + return this; + } + + public TransferCompletionHandler removeTransferListener(TransferListener t) { + listeners.remove(t); + return this; + } + + public void headers(HttpHeaders headers) { + this.headers = headers; + } + + @Override + public State onHeadersReceived(final HttpHeaders headers) throws Exception { + fireOnHeaderReceived(headers); + return super.onHeadersReceived(headers); + } + + @Override + public State onTrailingHeadersReceived(HttpHeaders headers) throws Exception { + fireOnHeaderReceived(headers); + return super.onHeadersReceived(headers); + } + + @Override + public State onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { + State s = State.CONTINUE; + if (accumulateResponseBytes) { + s = super.onBodyPartReceived(content); + } + fireOnBytesReceived(content.getBodyPartBytes()); + return s; + } + + @Override + public @Nullable Response onCompleted(@Nullable Response response) throws Exception { + fireOnEnd(); + return response; + } + + @Override + public State onHeadersWritten() { + if (headers != null) { + fireOnHeadersSent(headers); + } + return State.CONTINUE; + } + + @Override + public State onContentWriteProgress(long amount, long current, long total) { + fireOnBytesSent(amount, current, total); + return State.CONTINUE; + } + + @Override + public void onThrowable(Throwable t) { + fireOnThrowable(t); + } + + private void fireOnHeadersSent(HttpHeaders headers) { + for (TransferListener l : listeners) { + try { + l.onRequestHeadersSent(headers); + } catch (Throwable t) { + l.onThrowable(t); + } + } + } + + private void fireOnHeaderReceived(HttpHeaders headers) { + for (TransferListener l : listeners) { + try { + l.onResponseHeadersReceived(headers); + } catch (Throwable t) { + l.onThrowable(t); + } + } + } + + private void fireOnEnd() { + for (TransferListener l : listeners) { + try { + l.onRequestResponseCompleted(); + } catch (Throwable t) { + l.onThrowable(t); + } + } + } + + private void fireOnBytesReceived(byte[] b) { + for (TransferListener l : listeners) { + try { + l.onBytesReceived(b); + } catch (Throwable t) { + l.onThrowable(t); + } + } + } + + private void fireOnBytesSent(long amount, long current, long total) { + for (TransferListener l : listeners) { + try { + l.onBytesSent(amount, current, total); + } catch (Throwable t) { + l.onThrowable(t); + } + } + } + + private void fireOnThrowable(Throwable t) { + for (TransferListener l : listeners) { + try { + l.onThrowable(t); + } catch (Throwable t2) { + logger.warn("onThrowable", t2); + } + } + } +} diff --git a/api/src/main/java/org/asynchttpclient/listener/TransferListener.java b/client/src/main/java/org/asynchttpclient/handler/TransferListener.java similarity index 76% rename from api/src/main/java/org/asynchttpclient/listener/TransferListener.java rename to client/src/main/java/org/asynchttpclient/handler/TransferListener.java index 1043b3c561..fd50dcf7e6 100644 --- a/api/src/main/java/org/asynchttpclient/listener/TransferListener.java +++ b/client/src/main/java/org/asynchttpclient/handler/TransferListener.java @@ -10,33 +10,35 @@ * "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.listener; +package org.asynchttpclient.handler; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; - -import java.io.IOException; +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 { /** * Invoked when the request bytes are starting to get send. + * + * @param headers the headers */ - void onRequestHeadersSent(FluentCaseInsensitiveStringsMap headers); + void onRequestHeadersSent(HttpHeaders headers); /** * Invoked when the response bytes are starting to get received. + * + * @param headers the headers */ - void onResponseHeadersReceived(FluentCaseInsensitiveStringsMap headers); + void onResponseHeadersReceived(HttpHeaders headers); /** * Invoked every time response's chunk are received. * - * @param bytes a {@link byte[]} + * @param bytes a {@link byte} array */ - void onBytesReceived(byte[] bytes) throws IOException; + void onBytesReceived(byte[] bytes); /** * Invoked every time request's chunk are sent. diff --git a/client/src/main/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcessor.java b/client/src/main/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcessor.java new file mode 100644 index 0000000000..3a65f019e7 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcessor.java @@ -0,0 +1,113 @@ +/* + * 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.handler.resumable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.Collections; +import java.util.Map; +import java.util.Scanner; +import java.util.concurrent.ConcurrentHashMap; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.asynchttpclient.util.MiscUtils.closeSilently; + +/** + * A {@link ResumableAsyncHandler.ResumableProcessor} which use a properties file + * to store the download index information. + */ +public class PropertiesBasedResumableProcessor implements ResumableAsyncHandler.ResumableProcessor { + private static final Logger log = LoggerFactory.getLogger(PropertiesBasedResumableProcessor.class); + private static final File TMP = new File(System.getProperty("java.io.tmpdir"), "ahc"); + private static final String storeName = "ResumableAsyncHandler.properties"; + + private final ConcurrentHashMap properties = new ConcurrentHashMap<>(); + + private static String append(Map.Entry e) { + return e.getKey() + '=' + e.getValue() + '\n'; + } + + @Override + public void put(String url, long transferredBytes) { + properties.put(url, transferredBytes); + } + + @Override + public void remove(String uri) { + if (uri != null) { + properties.remove(uri); + } + } + + @Override + public void save(Map map) { + log.debug("Saving current download state {}", properties); + OutputStream os = null; + try { + + if (!TMP.exists() && !TMP.mkdirs()) { + throw new IllegalStateException("Unable to create directory: " + TMP.getAbsolutePath()); + } + File f = new File(TMP, storeName); + if (!f.exists() && !f.createNewFile()) { + throw new IllegalStateException("Unable to create temp file: " + f.getAbsolutePath()); + } + if (!f.canWrite()) { + throw new IllegalStateException(); + } + + os = Files.newOutputStream(f.toPath()); + for (Map.Entry e : properties.entrySet()) { + os.write(append(e).getBytes(UTF_8)); + } + os.flush(); + } catch (Throwable e) { + log.warn(e.getMessage(), e); + } finally { + closeSilently(os); + } + } + + @Override + public Map load() { + Scanner scan = null; + try { + scan = new Scanner(new File(TMP, storeName), UTF_8); + scan.useDelimiter("[=\n]"); + + String key; + String value; + while (scan.hasNext()) { + key = scan.next().trim(); + value = scan.next().trim(); + properties.put(key, Long.valueOf(value)); + } + log.debug("Loading previous download state {}", properties); + } catch (FileNotFoundException ex) { + log.debug("Missing {}", storeName); + } catch (Throwable ex) { + // Survive any exceptions + log.warn(ex.getMessage(), ex); + } finally { + if (scan != null) { + scan.close(); + } + } + return Collections.unmodifiableMap(properties); + } +} diff --git a/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java new file mode 100644 index 0000000000..6b8794547a --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java @@ -0,0 +1,324 @@ +/* + * 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.handler.resumable; + +import io.netty.handler.codec.http.HttpHeaders; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.Request; +import org.asynchttpclient.RequestBuilder; +import org.asynchttpclient.Response; +import org.asynchttpclient.Response.ResponseBuilder; +import org.asynchttpclient.handler.TransferCompletionHandler; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicLong; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +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 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. + *
+ * In case of a JVM crash/shutdown, you can create an instance of this class and pass the last valid bytes position. + *

+ * Beware that it registers a shutdown hook, that will cause a ClassLoader leak when used in an appserver and only redeploying the application. + */ +public class ResumableAsyncHandler implements AsyncHandler { + private static final Logger logger = LoggerFactory.getLogger(TransferCompletionHandler.class); + private static final ResumableIndexThread resumeIndexThread = new ResumableIndexThread(); + private static Map resumableIndex = Collections.emptyMap(); + + private final AtomicLong byteTransferred; + private final ResumableProcessor resumableProcessor; + private final @Nullable AsyncHandler decoratedAsyncHandler; + private final boolean accumulateBody; + private String url = ""; + private final ResponseBuilder responseBuilder = new ResponseBuilder(); + private ResumableListener resumableListener = new NULLResumableListener(); + + private ResumableAsyncHandler(long byteTransferred, @Nullable ResumableProcessor resumableProcessor, + @Nullable AsyncHandler decoratedAsyncHandler, boolean accumulateBody) { + + this.byteTransferred = new AtomicLong(byteTransferred); + + if (resumableProcessor == null) { + resumableProcessor = new NULLResumableHandler(); + } + this.resumableProcessor = resumableProcessor; + + resumableIndex = resumableProcessor.load(); + resumeIndexThread.addResumableProcessor(resumableProcessor); + + this.decoratedAsyncHandler = decoratedAsyncHandler; + this.accumulateBody = accumulateBody; + } + + public ResumableAsyncHandler(long byteTransferred) { + this(byteTransferred, null, null, false); + } + + public ResumableAsyncHandler(boolean accumulateBody) { + this(0, null, null, accumulateBody); + } + + public ResumableAsyncHandler() { + this(0, null, null, false); + } + + public ResumableAsyncHandler(AsyncHandler decoratedAsyncHandler) { + this(0, new PropertiesBasedResumableProcessor(), decoratedAsyncHandler, false); + } + + public ResumableAsyncHandler(long byteTransferred, AsyncHandler decoratedAsyncHandler) { + this(byteTransferred, new PropertiesBasedResumableProcessor(), decoratedAsyncHandler, false); + } + + public ResumableAsyncHandler(ResumableProcessor resumableProcessor) { + this(0, resumableProcessor, null, false); + } + + public ResumableAsyncHandler(ResumableProcessor resumableProcessor, boolean accumulateBody) { + this(0, resumableProcessor, null, accumulateBody); + } + + @Override + public State onStatusReceived(final HttpResponseStatus status) throws Exception { + responseBuilder.accumulate(status); + if (status.getStatusCode() == 200 || status.getStatusCode() == 206) { + url = status.getUri().toUrl(); + } else { + return AsyncHandler.State.ABORT; + } + + if (decoratedAsyncHandler != null) { + return decoratedAsyncHandler.onStatusReceived(status); + } + + return AsyncHandler.State.CONTINUE; + } + + @Override + public void onThrowable(Throwable t) { + if (decoratedAsyncHandler != null) { + decoratedAsyncHandler.onThrowable(t); + } else { + logger.debug("", t); + } + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { + + if (accumulateBody) { + responseBuilder.accumulate(bodyPart); + } + + State state = State.CONTINUE; + try { + resumableListener.onBytesReceived(bodyPart.getBodyByteBuffer()); + } catch (IOException ex) { + return AsyncHandler.State.ABORT; + } + + if (decoratedAsyncHandler != null) { + state = decoratedAsyncHandler.onBodyPartReceived(bodyPart); + } + + byteTransferred.addAndGet(bodyPart.getBodyPartBytes().length); + resumableProcessor.put(url, byteTransferred.get()); + + return state; + } + + @Override + public @Nullable Response onCompleted() throws Exception { + resumableProcessor.remove(url); + resumableListener.onAllBytesReceived(); + + if (decoratedAsyncHandler != null) { + decoratedAsyncHandler.onCompleted(); + } + // Not sure + return responseBuilder.build(); + } + + @Override + public State onHeadersReceived(HttpHeaders headers) throws Exception { + responseBuilder.accumulate(headers); + String contentLengthHeader = headers.get(CONTENT_LENGTH); + if (contentLengthHeader != null) { + if (Long.parseLong(contentLengthHeader) == -1L) { + return AsyncHandler.State.ABORT; + } + } + + if (decoratedAsyncHandler != null) { + return decoratedAsyncHandler.onHeadersReceived(headers); + } + return State.CONTINUE; + } + + @Override + public State onTrailingHeadersReceived(HttpHeaders headers) { + responseBuilder.accumulate(headers); + return State.CONTINUE; + } + + /** + * Invoke this API if you want to set the Range header on your {@link Request} based on the last valid bytes + * position. + * + * @param request {@link Request} + * @return a {@link Request} with the Range header properly set. + */ + public Request adjustRequestRange(Request request) { + Long ri = resumableIndex.get(request.getUrl()); + if (ri != null) { + byteTransferred.set(ri); + } + + // The Resumable + if (resumableListener != null && resumableListener.length() > 0 && byteTransferred.get() != resumableListener.length()) { + byteTransferred.set(resumableListener.length()); + } + + RequestBuilder builder = request.toBuilder(); + if (request.getHeaders().get(RANGE) == null && byteTransferred.get() != 0) { + builder.setHeader(RANGE, "bytes=" + byteTransferred.get() + '-'); + } + return builder.build(); + } + + /** + * Set a {@link ResumableListener} + * + * @param resumableListener a {@link ResumableListener} + * @return this + */ + public ResumableAsyncHandler setResumableListener(ResumableListener resumableListener) { + this.resumableListener = resumableListener; + return this; + } + + /** + * An interface to implement in order to manage the way the incomplete file management are handled. + */ + public interface ResumableProcessor { + + /** + * Associate a key with the number of bytes successfully transferred. + * + * @param key a key. The recommended way is to use an url. + * @param transferredBytes The number of bytes successfully transferred. + */ + void put(String key, long transferredBytes); + + /** + * Remove the key associate value. + * + * @param key key from which the value will be discarded + */ + void remove(String key); + + /** + * Save the current {@link Map} instance which contains information about the current transfer state. + * This method *only* invoked when the JVM is shutting down. + * + * @param map the current transfer state + */ + void save(Map map); + + /** + * Load the {@link Map} in memory, contains information about the transferred bytes. + * + * @return {@link Map} current transfer state + */ + Map load(); + } + + private static class ResumableIndexThread extends Thread { + + public final ConcurrentLinkedQueue resumableProcessors = new ConcurrentLinkedQueue<>(); + + private ResumableIndexThread() { + Runtime.getRuntime().addShutdownHook(this); + } + + public void addResumableProcessor(ResumableProcessor p) { + resumableProcessors.offer(p); + } + + @Override + public void run() { + for (ResumableProcessor p : resumableProcessors) { + p.save(resumableIndex); + } + } + } + + private static class NULLResumableHandler implements ResumableProcessor { + + @Override + public void put(String url, long transferredBytes) { + } + + @Override + public void remove(String uri) { + } + + @Override + public void save(Map map) { + } + + @Override + public Map load() { + return new HashMap<>(); + } + } + + private static class NULLResumableListener implements ResumableListener { + + private long length; + + private NULLResumableListener() { + length = 0L; + } + + @Override + public void onBytesReceived(ByteBuffer byteBuffer) { + length += byteBuffer.remaining(); + } + + @Override + public void onAllBytesReceived() { + } + + @Override + public long length() { + return length; + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableIOExceptionFilter.java b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableIOExceptionFilter.java new file mode 100644 index 0000000000..190ec6a3a7 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableIOExceptionFilter.java @@ -0,0 +1,32 @@ +/* + * 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.handler.resumable; + +import org.asynchttpclient.Request; +import org.asynchttpclient.filter.FilterContext; +import org.asynchttpclient.filter.IOExceptionFilter; + +/** + * Simple {@link IOExceptionFilter} that replay the current {@link Request} using a {@link ResumableAsyncHandler} + */ +public class ResumableIOExceptionFilter implements IOExceptionFilter { + + @Override + public FilterContext filter(FilterContext ctx) { + if (ctx.getIOException() != null && ctx.getAsyncHandler() instanceof ResumableAsyncHandler) { + Request request = ((ResumableAsyncHandler) ctx.getAsyncHandler()).adjustRequestRange(ctx.getRequest()); + return new FilterContext.FilterContextBuilder<>(ctx).request(request).replayRequest(true).build(); + } + return ctx; + } +} diff --git a/api/src/main/java/org/asynchttpclient/resumable/ResumableListener.java b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableListener.java similarity index 87% rename from api/src/main/java/org/asynchttpclient/resumable/ResumableListener.java rename to client/src/main/java/org/asynchttpclient/handler/resumable/ResumableListener.java index c8af9c6561..4c4c6cbffd 100644 --- a/api/src/main/java/org/asynchttpclient/resumable/ResumableListener.java +++ b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableListener.java @@ -10,7 +10,7 @@ * "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.resumable; +package org.asynchttpclient.handler.resumable; import java.io.IOException; import java.nio.ByteBuffer; @@ -24,12 +24,12 @@ public interface ResumableListener { * Invoked when some bytes are available to digest. * * @param byteBuffer the current bytes - * @throws IOException + * @throws IOException exception while writing the byteBuffer */ void onBytesReceived(ByteBuffer byteBuffer) throws IOException; /** - * Invoked when all the bytes has been sucessfully transferred. + * Invoked when all the bytes has been successfully transferred. */ void onAllBytesReceived(); diff --git a/api/src/main/java/org/asynchttpclient/extra/ResumableRandomAccessFileListener.java b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableRandomAccessFileListener.java similarity index 85% rename from api/src/main/java/org/asynchttpclient/extra/ResumableRandomAccessFileListener.java rename to client/src/main/java/org/asynchttpclient/handler/resumable/ResumableRandomAccessFileListener.java index 7b0dd64b95..f7e28f6f64 100644 --- a/api/src/main/java/org/asynchttpclient/extra/ResumableRandomAccessFileListener.java +++ b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableRandomAccessFileListener.java @@ -10,18 +10,16 @@ * "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.extra; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; - -import org.asynchttpclient.resumable.ResumableListener; +package org.asynchttpclient.handler.resumable; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; +import static org.asynchttpclient.util.MiscUtils.closeSilently; + /** - * A {@link org.asynchttpclient.resumable.ResumableListener} which use a {@link RandomAccessFile} for storing the received bytes. + * A {@link ResumableListener} which use a {@link RandomAccessFile} for storing the received bytes. */ public class ResumableRandomAccessFileListener implements ResumableListener { private final RandomAccessFile file; @@ -35,8 +33,9 @@ public ResumableRandomAccessFileListener(RandomAccessFile file) { * resumable file download. * * @param buffer a {@link ByteBuffer} - * @throws IOException + * @throws IOException exception while writing into the file */ + @Override public void onBytesReceived(ByteBuffer buffer) throws IOException { file.seek(file.length()); if (buffer.hasArray()) { @@ -50,21 +49,17 @@ public void onBytesReceived(ByteBuffer buffer) throws IOException { } } - /** - * {@inheritDoc} - */ + @Override public void onAllBytesReceived() { closeSilently(file); } - /** - * {@inheritDoc} - */ + @Override public long length() { try { return file.length(); } catch (IOException e) { - return 0; + return -1; } } } diff --git a/client/src/main/java/org/asynchttpclient/netty/DiscardEvent.java b/client/src/main/java/org/asynchttpclient/netty/DiscardEvent.java new file mode 100644 index 0000000000..7983122572 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/DiscardEvent.java @@ -0,0 +1,23 @@ +/* + * 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. + * 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; + +/** + * Simple marker for stopping publishing bytes + */ +public enum DiscardEvent { + DISCARD +} diff --git a/client/src/main/java/org/asynchttpclient/netty/EagerResponseBodyPart.java b/client/src/main/java/org/asynchttpclient/netty/EagerResponseBodyPart.java new file mode 100755 index 0000000000..8247379b08 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/EagerResponseBodyPart.java @@ -0,0 +1,62 @@ +/* + * 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. + * 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; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import org.asynchttpclient.HttpResponseBodyPart; + +import java.nio.ByteBuffer; + +/** + * A callback class used when an HTTP response body is received. + * Bytes are eagerly fetched from the ByteBuf + */ +public class EagerResponseBodyPart extends HttpResponseBodyPart { + + private final byte[] bytes; + + public EagerResponseBodyPart(ByteBuf buf, boolean last) { + super(last); + bytes = ByteBufUtil.getBytes(buf); + } + + /** + * Return the response body's part bytes received. + * + * @return the response body's part bytes received. + */ + @Override + public byte[] getBodyPartBytes() { + return bytes; + } + + @Override + public int length() { + return bytes.length; + } + + @Override + 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 new file mode 100755 index 0000000000..e472770061 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/LazyResponseBodyPart.java @@ -0,0 +1,60 @@ +/* + * 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. + * 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; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import org.asynchttpclient.HttpResponseBodyPart; + +import java.nio.ByteBuffer; + +/** + * A callback class used when an HTTP response body is received. + */ +public class LazyResponseBodyPart extends HttpResponseBodyPart { + + private final ByteBuf buf; + + public LazyResponseBodyPart(ByteBuf buf, boolean last) { + super(last); + this.buf = buf; + } + + @Override + public ByteBuf getBodyByteBuf() { + return buf; + } + + @Override + public int length() { + return buf.readableBytes(); + } + + /** + * Return the response body's part bytes received. + * + * @return the response body's part bytes received. + */ + @Override + public byte[] getBodyPartBytes() { + return ByteBufUtil.getBytes(buf.duplicate()); + } + + @Override + public ByteBuffer getBodyByteBuffer() { + return buf.nioBuffer(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java b/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java new file mode 100755 index 0000000000..61fb15161c --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/NettyResponse.java @@ -0,0 +1,235 @@ +/* + * 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. + * 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; + +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; +import io.netty.handler.codec.http.cookie.Cookie; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.Response; +import org.asynchttpclient.uri.Uri; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.netty.handler.codec.http.HttpHeaderNames.SET_COOKIE; +import static io.netty.handler.codec.http.HttpHeaderNames.SET_COOKIE2; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.asynchttpclient.util.HttpUtils.extractContentTypeCharsetAttribute; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.withDefault; + +/** + * Wrapper around the {@link Response} API. + */ +public class NettyResponse implements Response { + + private final List bodyParts; + private final HttpHeaders headers; + private final HttpResponseStatus status; + private List cookies; + + public NettyResponse(HttpResponseStatus status, + HttpHeaders headers, + List bodyParts) { + this.bodyParts = bodyParts; + this.headers = headers; + this.status = status; + } + + private List buildCookies() { + + List setCookieHeaders = headers.getAll(SET_COOKIE2); + + if (!isNonEmpty(setCookieHeaders)) { + setCookieHeaders = headers.getAll(SET_COOKIE); + } + + if (isNonEmpty(setCookieHeaders)) { + List cookies = new ArrayList<>(1); + for (String value : setCookieHeaders) { + Cookie c = ClientCookieDecoder.STRICT.decode(value); + if (c != null) { + cookies.add(c); + } + } + return Collections.unmodifiableList(cookies); + } + + return Collections.emptyList(); + } + + @Override + public final int getStatusCode() { + return status.getStatusCode(); + } + + @Override + public final String getStatusText() { + return status.getStatusText(); + } + + @Override + public final Uri getUri() { + return status.getUri(); + } + + @Override + public SocketAddress getRemoteAddress() { + return status.getRemoteAddress(); + } + + @Override + public SocketAddress getLocalAddress() { + return status.getLocalAddress(); + } + + @Override + public final String getContentType() { + return headers != null ? getHeader(CONTENT_TYPE) : null; + } + + @Override + public final String getHeader(CharSequence name) { + return headers != null ? getHeaders().get(name) : null; + } + + @Override + public final List getHeaders(CharSequence name) { + return headers != null ? getHeaders().getAll(name) : Collections.emptyList(); + } + + @Override + public final HttpHeaders getHeaders() { + return headers != null ? headers : EmptyHttpHeaders.INSTANCE; + } + + @Override + public final boolean isRedirected() { + switch (status.getStatusCode()) { + case 301: + case 302: + case 303: + case 307: + case 308: + return true; + default: + return false; + } + } + + @Override + public List getCookies() { + + if (headers == null) { + return Collections.emptyList(); + } + + if (cookies == null) { + cookies = buildCookies(); + } + return cookies; + + } + + @Override + public boolean hasResponseStatus() { + return status != null; + } + + @Override + public boolean hasResponseHeaders() { + return headers != null && !headers.isEmpty(); + } + + @Override + public boolean hasResponseBody() { + return isNonEmpty(bodyParts); + } + + @Override + public byte[] getResponseBodyAsBytes() { + return getResponseBodyAsByteBuffer().array(); + } + + @Override + public ByteBuffer getResponseBodyAsByteBuffer() { + + int length = 0; + for (HttpResponseBodyPart part : bodyParts) { + length += part.length(); + } + + ByteBuffer target = ByteBuffer.wrap(new byte[length]); + for (HttpResponseBodyPart part : bodyParts) { + target.put(part.getBodyPartBytes()); + } + + target.flip(); + 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)); + } + + @Override + public String getResponseBody(Charset charset) { + return new String(getResponseBodyAsBytes(), charset); + } + + @Override + public InputStream getResponseBodyAsStream() { + return new ByteArrayInputStream(getResponseBodyAsBytes()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(getClass().getSimpleName()).append(" {\n") + .append("\tstatusCode=").append(getStatusCode()).append('\n') + .append("\theaders=\n"); + + for (Map.Entry header : getHeaders()) { + sb.append("\t\t").append(header.getKey()).append(": ").append(header.getValue()).append('\n'); + } + return sb.append("\tbody=\n").append(getResponseBody()).append('\n') + .append('}').toString(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java b/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java new file mode 100755 index 0000000000..c29c0f33d9 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java @@ -0,0 +1,560 @@ +/* + * 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. + * 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; + +import io.netty.channel.Channel; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.ListenableFuture; +import org.asynchttpclient.Realm; +import org.asynchttpclient.Request; +import org.asynchttpclient.channel.ChannelPoolPartitioning; +import org.asynchttpclient.netty.channel.ChannelState; +import org.asynchttpclient.netty.channel.Channels; +import org.asynchttpclient.netty.channel.ConnectionSemaphore; +import org.asynchttpclient.netty.request.NettyRequest; +import org.asynchttpclient.netty.timeout.TimeoutsHolder; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.uri.Uri; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import static org.asynchttpclient.util.DateUtils.unpreciseMillisTime; + +/** + * A {@link Future} that can be used to track when an asynchronous HTTP request + * has been fully processed. + * + * @param the result type + */ +public final class NettyResponseFuture implements ListenableFuture { + + private static final Logger LOGGER = LoggerFactory.getLogger(NettyResponseFuture.class); + + @SuppressWarnings("rawtypes") + private static final AtomicIntegerFieldUpdater REDIRECT_COUNT_UPDATER = AtomicIntegerFieldUpdater + .newUpdater(NettyResponseFuture.class, "redirectCount"); + @SuppressWarnings("rawtypes") + private static final AtomicIntegerFieldUpdater CURRENT_RETRY_UPDATER = AtomicIntegerFieldUpdater + .newUpdater(NettyResponseFuture.class, "currentRetry"); + @SuppressWarnings("rawtypes") + private static final AtomicIntegerFieldUpdater IS_DONE_FIELD = AtomicIntegerFieldUpdater + .newUpdater(NettyResponseFuture.class, "isDone"); + @SuppressWarnings("rawtypes") + private static final AtomicIntegerFieldUpdater IS_CANCELLED_FIELD = AtomicIntegerFieldUpdater + .newUpdater(NettyResponseFuture.class, "isCancelled"); + @SuppressWarnings("rawtypes") + private static final AtomicIntegerFieldUpdater IN_AUTH_FIELD = AtomicIntegerFieldUpdater + .newUpdater(NettyResponseFuture.class, "inAuth"); + @SuppressWarnings("rawtypes") + private static final AtomicIntegerFieldUpdater IN_PROXY_AUTH_FIELD = AtomicIntegerFieldUpdater + .newUpdater(NettyResponseFuture.class, "inProxyAuth"); + @SuppressWarnings("rawtypes") + private static final AtomicIntegerFieldUpdater CONTENT_PROCESSED_FIELD = AtomicIntegerFieldUpdater + .newUpdater(NettyResponseFuture.class, "contentProcessed"); + @SuppressWarnings("rawtypes") + private static final AtomicIntegerFieldUpdater ON_THROWABLE_CALLED_FIELD = AtomicIntegerFieldUpdater + .newUpdater(NettyResponseFuture.class, "onThrowableCalled"); + @SuppressWarnings("rawtypes") + private static final AtomicReferenceFieldUpdater TIMEOUTS_HOLDER_FIELD = AtomicReferenceFieldUpdater + .newUpdater(NettyResponseFuture.class, TimeoutsHolder.class, "timeoutsHolder"); + @SuppressWarnings("rawtypes") + private static final AtomicReferenceFieldUpdater PARTITION_KEY_LOCK_FIELD = AtomicReferenceFieldUpdater + .newUpdater(NettyResponseFuture.class, Object.class, "partitionKeyLock"); + + private final long start = unpreciseMillisTime(); + private final ChannelPoolPartitioning connectionPoolPartitioning; + private final ConnectionSemaphore connectionSemaphore; + private final ProxyServer proxyServer; + private final int maxRetry; + private final CompletableFuture future = new CompletableFuture<>(); + public Throwable pendingException; + // state mutated from outside the event loop + // TODO check if they are indeed mutated outside the event loop + private volatile int isDone; + private volatile int isCancelled; + private volatile int inAuth; + private volatile int inProxyAuth; + @SuppressWarnings("unused") + private volatile int contentProcessed; + @SuppressWarnings("unused") + private volatile int onThrowableCalled; + @SuppressWarnings("unused") + private volatile TimeoutsHolder timeoutsHolder; + // partition key, when != null used to release lock in ChannelManager + private volatile Object partitionKeyLock; + // volatile where we need CAS ops + private volatile int redirectCount; + private volatile int currentRetry; + // volatile where we don't need CAS ops + private volatile long touch = unpreciseMillisTime(); + private volatile ChannelState channelState = ChannelState.NEW; + // state mutated only inside the event loop + private Channel channel; + private boolean keepAlive = true; + private Request targetRequest; + private Request currentRequest; + private NettyRequest nettyRequest; + private AsyncHandler asyncHandler; + private boolean streamAlreadyConsumed; + private boolean reuseChannel; + private boolean headersAlreadyWrittenOnContinue; + private boolean dontWriteBodyBecauseExpectContinue; + private boolean allowConnect; + private Realm realm; + private Realm proxyRealm; + + public NettyResponseFuture(Request originalRequest, + AsyncHandler asyncHandler, + NettyRequest nettyRequest, + int maxRetry, + ChannelPoolPartitioning connectionPoolPartitioning, + ConnectionSemaphore connectionSemaphore, + ProxyServer proxyServer) { + + this.asyncHandler = asyncHandler; + targetRequest = currentRequest = originalRequest; + this.nettyRequest = nettyRequest; + this.connectionPoolPartitioning = connectionPoolPartitioning; + this.connectionSemaphore = connectionSemaphore; + this.proxyServer = proxyServer; + this.maxRetry = maxRetry; + } + + private void releasePartitionKeyLock() { + if (connectionSemaphore == null) { + return; + } + + Object partitionKey = takePartitionKeyLock(); + if (partitionKey != null) { + connectionSemaphore.releaseChannelLock(partitionKey); + } + } + + // Take partition key lock object, + // but do not release channel lock. + public Object takePartitionKeyLock() { + // shortcut, much faster than getAndSet + if (partitionKeyLock == null) { + return null; + } + + return PARTITION_KEY_LOCK_FIELD.getAndSet(this, null); + } + + // java.util.concurrent.Future + + @Override + public boolean isDone() { + return isDone != 0 || isCancelled(); + } + + @Override + public boolean isCancelled() { + return isCancelled != 0; + } + + @Override + public boolean cancel(boolean force) { + releasePartitionKeyLock(); + cancelTimeouts(); + + if (IS_CANCELLED_FIELD.getAndSet(this, 1) != 0) { + return false; + } + + 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) { + try { + asyncHandler.onThrowable(new CancellationException()); + } catch (Throwable t) { + LOGGER.warn("cancel", t); + } + } + + future.cancel(false); + return true; + } + + @Override + public V get() throws InterruptedException, ExecutionException { + return future.get(); + } + + @Override + public V get(long l, TimeUnit tu) throws InterruptedException, TimeoutException, ExecutionException { + return future.get(l, tu); + } + + private void loadContent() throws ExecutionException { + if (future.isDone()) { + try { + future.get(); + } catch (InterruptedException e) { + throw new RuntimeException("unreachable", e); + } + } + + // No more retry + CURRENT_RETRY_UPDATER.set(this, maxRetry); + if (CONTENT_PROCESSED_FIELD.getAndSet(this, 1) == 0) { + try { + future.complete(asyncHandler.onCompleted()); + } catch (Throwable ex) { + if (ON_THROWABLE_CALLED_FIELD.getAndSet(this, 1) == 0) { + try { + try { + asyncHandler.onThrowable(ex); + } catch (Throwable t) { + LOGGER.debug("asyncHandler.onThrowable", t); + } + } finally { + cancelTimeouts(); + } + } + future.completeExceptionally(ex); + } + } + future.getNow(null); + } + + // org.asynchttpclient.ListenableFuture + + private boolean terminateAndExit() { + releasePartitionKeyLock(); + cancelTimeouts(); + channel = null; + reuseChannel = false; + return IS_DONE_FIELD.getAndSet(this, 1) != 0 || isCancelled != 0; + } + + @Override + public void done() { + + if (terminateAndExit()) { + return; + } + + try { + loadContent(); + } catch (ExecutionException ignored) { + + } catch (RuntimeException t) { + future.completeExceptionally(t); + } catch (Throwable t) { + future.completeExceptionally(t); + throw t; + } + } + + @Override + public void abort(final Throwable t) { + + if (terminateAndExit()) { + return; + } + + future.completeExceptionally(t); + + if (ON_THROWABLE_CALLED_FIELD.compareAndSet(this, 0, 1)) { + try { + asyncHandler.onThrowable(t); + } catch (Throwable te) { + LOGGER.debug("asyncHandler.onThrowable", te); + } + } + } + + @Override + public void touch() { + touch = unpreciseMillisTime(); + } + + @Override + public ListenableFuture addListener(Runnable listener, Executor exec) { + if (exec == null) { + exec = Runnable::run; + } + future.whenCompleteAsync((r, v) -> listener.run(), exec); + return this; + } + + @Override + public CompletableFuture toCompletableFuture() { + return future; + } + + // INTERNAL + + public Uri getUri() { + return targetRequest.getUri(); + } + + public ProxyServer getProxyServer() { + return proxyServer; + } + + public void cancelTimeouts() { + TimeoutsHolder ref = TIMEOUTS_HOLDER_FIELD.getAndSet(this, null); + if (ref != null) { + ref.cancel(); + } + } + + public Request getTargetRequest() { + return targetRequest; + } + + public void setTargetRequest(Request targetRequest) { + this.targetRequest = targetRequest; + } + + public Request getCurrentRequest() { + return currentRequest; + } + + public void setCurrentRequest(Request currentRequest) { + this.currentRequest = currentRequest; + } + + public NettyRequest getNettyRequest() { + return nettyRequest; + } + + public void setNettyRequest(NettyRequest nettyRequest) { + this.nettyRequest = nettyRequest; + } + + public AsyncHandler getAsyncHandler() { + return asyncHandler; + } + + public void setAsyncHandler(AsyncHandler asyncHandler) { + this.asyncHandler = asyncHandler; + } + + public boolean isKeepAlive() { + return keepAlive; + } + + public void setKeepAlive(final boolean keepAlive) { + this.keepAlive = keepAlive; + } + + public int incrementAndGetCurrentRedirectCount() { + return REDIRECT_COUNT_UPDATER.incrementAndGet(this); + } + + public TimeoutsHolder getTimeoutsHolder() { + return TIMEOUTS_HOLDER_FIELD.get(this); + } + + public void setTimeoutsHolder(TimeoutsHolder timeoutsHolder) { + TimeoutsHolder ref = TIMEOUTS_HOLDER_FIELD.getAndSet(this, timeoutsHolder); + if (ref != null) { + ref.cancel(); + } + } + + public boolean isInAuth() { + return inAuth != 0; + } + + public void setInAuth(boolean inAuth) { + this.inAuth = inAuth ? 1 : 0; + } + + public boolean isAndSetInAuth(boolean set) { + return IN_AUTH_FIELD.getAndSet(this, set ? 1 : 0) != 0; + } + + public boolean isInProxyAuth() { + return inProxyAuth != 0; + } + + public void setInProxyAuth(boolean inProxyAuth) { + this.inProxyAuth = inProxyAuth ? 1 : 0; + } + + public boolean isAndSetInProxyAuth(boolean inProxyAuth) { + return IN_PROXY_AUTH_FIELD.getAndSet(this, inProxyAuth ? 1 : 0) != 0; + } + + public ChannelState getChannelState() { + return channelState; + } + + public void setChannelState(ChannelState channelState) { + this.channelState = channelState; + } + + public boolean isStreamConsumed() { + return streamAlreadyConsumed; + } + + public void setStreamConsumed(boolean streamConsumed) { + streamAlreadyConsumed = streamConsumed; + } + + public long getLastTouch() { + return touch; + } + + public boolean isHeadersAlreadyWrittenOnContinue() { + return headersAlreadyWrittenOnContinue; + } + + public void setHeadersAlreadyWrittenOnContinue(boolean headersAlreadyWrittenOnContinue) { + this.headersAlreadyWrittenOnContinue = headersAlreadyWrittenOnContinue; + } + + public boolean isDontWriteBodyBecauseExpectContinue() { + return dontWriteBodyBecauseExpectContinue; + } + + public void setDontWriteBodyBecauseExpectContinue(boolean dontWriteBodyBecauseExpectContinue) { + this.dontWriteBodyBecauseExpectContinue = dontWriteBodyBecauseExpectContinue; + } + + public boolean isConnectAllowed() { + return allowConnect; + } + + public void setConnectAllowed(boolean allowConnect) { + this.allowConnect = allowConnect; + } + + public void attachChannel(Channel channel, boolean reuseChannel) { + + // future could have been cancelled first + if (isDone()) { + Channels.silentlyCloseChannel(channel); + } + + this.channel = channel; + this.reuseChannel = reuseChannel; + } + + public Channel channel() { + return channel; + } + + public boolean isReuseChannel() { + return reuseChannel; + } + + public void setReuseChannel(boolean reuseChannel) { + this.reuseChannel = reuseChannel; + } + + public boolean incrementRetryAndCheck() { + return maxRetry > 0 && CURRENT_RETRY_UPDATER.incrementAndGet(this) <= maxRetry; + } + + /** + * Return true if the {@link Future} can be recovered. There is some scenario + * where a connection can be closed by an unexpected IOException, and in some + * situation we can recover from that exception. + * + * @return true if that {@link Future} cannot be recovered. + */ + public boolean isReplayPossible() { + return !isDone() && !(Channels.isChannelActive(channel) && !"https".equalsIgnoreCase(getUri().getScheme())) + && inAuth == 0 && inProxyAuth == 0; + } + + public long getStart() { + return start; + } + + public Object getPartitionKey() { + return connectionPoolPartitioning.getPartitionKey(targetRequest.getUri(), targetRequest.getVirtualHost(), + proxyServer); + } + + public void acquirePartitionLockLazily() throws IOException { + if (connectionSemaphore == null || partitionKeyLock != null) { + return; + } + + Object partitionKey = getPartitionKey(); + connectionSemaphore.acquireChannelLock(partitionKey); + Object prevKey = PARTITION_KEY_LOCK_FIELD.getAndSet(this, partitionKey); + if (prevKey != null) { + // self-check + + connectionSemaphore.releaseChannelLock(prevKey); + releasePartitionKeyLock(); + + throw new IllegalStateException("Trying to acquire partition lock concurrently. Please report."); + } + + if (isDone()) { + // may be cancelled while we acquired a lock + releasePartitionKeyLock(); + } + } + + public Realm getRealm() { + return realm; + } + + public void setRealm(Realm realm) { + this.realm = realm; + } + + public Realm getProxyRealm() { + return proxyRealm; + } + + public void setProxyRealm(Realm proxyRealm) { + this.proxyRealm = proxyRealm; + } + + @Override + public String toString() { + return "NettyResponseFuture{" + // + "currentRetry=" + currentRetry + // + ",\n\tisDone=" + isDone + // + ",\n\tisCancelled=" + isCancelled + // + ",\n\tasyncHandler=" + asyncHandler + // + ",\n\tnettyRequest=" + nettyRequest + // + ",\n\tfuture=" + future + // + ",\n\turi=" + getUri() + // + ",\n\tkeepAlive=" + keepAlive + // + ",\n\tredirectCount=" + redirectCount + // + ",\n\ttimeoutsHolder=" + TIMEOUTS_HOLDER_FIELD.get(this) + // + ",\n\tinAuth=" + inAuth + // + ",\n\ttouch=" + touch + // + '}'; + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java b/client/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java new file mode 100755 index 0000000000..567432af3b --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java @@ -0,0 +1,95 @@ +/* + * 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. + * 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; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.HttpResponse; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.uri.Uri; + +import java.net.SocketAddress; + +/** + * A class that represent the HTTP response status line (code + text) + */ +public class NettyResponseStatus extends HttpResponseStatus { + + private final HttpResponse response; + private final SocketAddress remoteAddress; + private final SocketAddress localAddress; + + public NettyResponseStatus(Uri uri, HttpResponse response, Channel channel) { + super(uri); + this.response = response; + if (channel != null) { + remoteAddress = channel.remoteAddress(); + localAddress = channel.localAddress(); + } else { + remoteAddress = null; + localAddress = null; + } + } + + /** + * Return the response status code + * + * @return the response status code + */ + @Override + public int getStatusCode() { + return response.status().code(); + } + + /** + * Return the response status text + * + * @return the response status text + */ + @Override + public String getStatusText() { + return response.status().reasonPhrase(); + } + + @Override + public String getProtocolName() { + return response.protocolVersion().protocolName(); + } + + @Override + public int getProtocolMajorVersion() { + return response.protocolVersion().majorVersion(); + } + + @Override + public int getProtocolMinorVersion() { + return response.protocolVersion().minorVersion(); + } + + @Override + public String getProtocolText() { + return response.protocolVersion().text(); + } + + @Override + public SocketAddress getRemoteAddress() { + return remoteAddress; + } + + @Override + public SocketAddress getLocalAddress() { + return localAddress; + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/OnLastHttpContentCallback.java b/client/src/main/java/org/asynchttpclient/netty/OnLastHttpContentCallback.java new file mode 100644 index 0000000000..15c0c96174 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/OnLastHttpContentCallback.java @@ -0,0 +1,31 @@ +/* + * 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. + * 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; + +public abstract class OnLastHttpContentCallback { + + protected final NettyResponseFuture future; + + protected OnLastHttpContentCallback(NettyResponseFuture future) { + this.future = future; + } + + public abstract void call() throws Exception; + + public NettyResponseFuture future() { + return future; + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/SimpleChannelFutureListener.java b/client/src/main/java/org/asynchttpclient/netty/SimpleChannelFutureListener.java new file mode 100644 index 0000000000..e4271c5367 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/SimpleChannelFutureListener.java @@ -0,0 +1,37 @@ +/* + * 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; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; + +public abstract class SimpleChannelFutureListener implements ChannelFutureListener { + + @Override + public final void operationComplete(ChannelFuture future) { + Channel channel = future.channel(); + if (future.isSuccess()) { + onSuccess(channel); + } else { + onFailure(channel, future.cause()); + } + } + + public abstract void onSuccess(Channel channel); + + public abstract void onFailure(Channel channel, Throwable cause); +} diff --git a/client/src/main/java/org/asynchttpclient/netty/SimpleFutureListener.java b/client/src/main/java/org/asynchttpclient/netty/SimpleFutureListener.java new file mode 100644 index 0000000000..357f572441 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/SimpleFutureListener.java @@ -0,0 +1,35 @@ +/* + * 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; + +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.FutureListener; + +public abstract class SimpleFutureListener implements FutureListener { + + @Override + public final void operationComplete(Future future) throws Exception { + if (future.isSuccess()) { + onSuccess(future.getNow()); + } else { + onFailure(future.cause()); + } + } + + protected abstract void onSuccess(V value) throws Exception; + + protected abstract void onFailure(Throwable t) throws Exception; +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java new file mode 100755 index 0000000000..c5c94c551c --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java @@ -0,0 +1,557 @@ +/* + * 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. + * 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.channel; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFactory; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.ChannelGroupFuture; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpContentDecompressor; +import io.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder; +import io.netty.handler.codec.http.websocketx.WebSocket08FrameEncoder; +import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator; +import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.netty.handler.proxy.ProxyHandler; +import io.netty.handler.proxy.Socks4ProxyHandler; +import io.netty.handler.proxy.Socks5ProxyHandler; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.stream.ChunkedWriteHandler; +import io.netty.resolver.NameResolver; +import io.netty.util.Timer; +import io.netty.util.concurrent.DefaultThreadFactory; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GlobalEventExecutor; +import io.netty.util.concurrent.ImmediateEventExecutor; +import io.netty.util.concurrent.Promise; +import io.netty.util.internal.PlatformDependent; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.ClientStats; +import org.asynchttpclient.HostStats; +import org.asynchttpclient.Realm; +import org.asynchttpclient.SslEngineFactory; +import org.asynchttpclient.channel.ChannelPool; +import org.asynchttpclient.channel.ChannelPoolPartitioning; +import org.asynchttpclient.channel.NoopChannelPool; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.OnLastHttpContentCallback; +import org.asynchttpclient.netty.handler.AsyncHttpClientHandler; +import org.asynchttpclient.netty.handler.HttpHandler; +import org.asynchttpclient.netty.handler.WebSocketHandler; +import org.asynchttpclient.netty.request.NettyRequestSender; +import org.asynchttpclient.netty.ssl.DefaultSslEngineFactory; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.uri.Uri; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ChannelManager { + + public static final String HTTP_CLIENT_CODEC = "http"; + public static final String SSL_HANDLER = "ssl"; + public static final String SOCKS_HANDLER = "socks"; + public static final String INFLATER_HANDLER = "inflater"; + public static final String CHUNKED_WRITER_HANDLER = "chunked-writer"; + public static final String WS_DECODER_HANDLER = "ws-decoder"; + public static final String WS_FRAME_AGGREGATOR = "ws-aggregator"; + public static final String WS_COMPRESSOR_HANDLER = "ws-compressor"; + public static final String WS_ENCODER_HANDLER = "ws-encoder"; + public static final String AHC_HTTP_HANDLER = "ahc-http"; + public static final String AHC_WS_HANDLER = "ahc-ws"; + public static final String LOGGING_HANDLER = "logging"; + private static final Logger LOGGER = LoggerFactory.getLogger(ChannelManager.class); + private final AsyncHttpClientConfig config; + private final SslEngineFactory sslEngineFactory; + private final EventLoopGroup eventLoopGroup; + private final boolean allowReleaseEventLoopGroup; + private final Bootstrap httpBootstrap; + private final Bootstrap wsBootstrap; + private final long handshakeTimeout; + + private final ChannelPool channelPool; + private final ChannelGroup openChannels; + + private AsyncHttpClientHandler wsHandler; + + private boolean isInstanceof(Object object, String name) { + final Class clazz; + try { + clazz = Class.forName(name, false, getClass().getClassLoader()); + } catch (ClassNotFoundException ignored) { + return false; + } + return clazz.isInstance(object); + } + + public ChannelManager(final AsyncHttpClientConfig config, Timer nettyTimer) { + this.config = config; + + sslEngineFactory = config.getSslEngineFactory() != null ? config.getSslEngineFactory() : new DefaultSslEngineFactory(); + try { + sslEngineFactory.init(config); + } catch (SSLException e) { + throw new RuntimeException("Could not initialize SslEngineFactory", e); + } + + ChannelPool channelPool = config.getChannelPool(); + if (channelPool == null) { + if (config.isKeepAlive()) { + channelPool = new DefaultChannelPool(config, nettyTimer); + } else { + channelPool = NoopChannelPool.INSTANCE; + } + } + + this.channelPool = channelPool; + openChannels = new DefaultChannelGroup("asyncHttpClient", GlobalEventExecutor.INSTANCE); + handshakeTimeout = config.getHandshakeTimeout(); + + // check if external EventLoopGroup is defined + ThreadFactory threadFactory = config.getThreadFactory() != null ? config.getThreadFactory() : new DefaultThreadFactory(config.getThreadPoolName()); + allowReleaseEventLoopGroup = config.getEventLoopGroup() == null; + TransportFactory transportFactory; + + if (allowReleaseEventLoopGroup) { + if (config.isUseNativeTransport()) { + transportFactory = getNativeTransportFactory(config); + } else { + transportFactory = NioTransportFactory.INSTANCE; + } + eventLoopGroup = transportFactory.newEventLoopGroup(config.getIoThreadsCount(), threadFactory); + } else { + eventLoopGroup = config.getEventLoopGroup(); + + if (eventLoopGroup instanceof NioEventLoopGroup) { + transportFactory = NioTransportFactory.INSTANCE; + } else if (isInstanceof(eventLoopGroup, "io.netty.channel.epoll.EpollEventLoopGroup")) { + transportFactory = new EpollTransportFactory(); + } else if (isInstanceof(eventLoopGroup, "io.netty.channel.kqueue.KQueueEventLoopGroup")) { + transportFactory = new KQueueTransportFactory(); + } else if (isInstanceof(eventLoopGroup, "io.netty.incubator.channel.uring.IOUringEventLoopGroup")) { + transportFactory = new IoUringIncubatorTransportFactory(); + } else { + throw new IllegalArgumentException("Unknown event loop group " + eventLoopGroup.getClass().getSimpleName()); + } + } + + httpBootstrap = newBootstrap(transportFactory, eventLoopGroup, config); + wsBootstrap = newBootstrap(transportFactory, eventLoopGroup, config); + } + + private static TransportFactory getNativeTransportFactory(AsyncHttpClientConfig config) { + // If we are running on macOS then use KQueue + if (PlatformDependent.isOsx()) { + if (KQueueTransportFactory.isAvailable()) { + return new KQueueTransportFactory(); + } + } + + // If we're not running on Windows then we're probably running on Linux. + // We will check if Io_Uring is available or not. If available, return IoUringIncubatorTransportFactory. + // Else + // We will check if Epoll is available or not. If available, return EpollTransportFactory. + // If none of the condition matches then no native transport is available, and we will throw an exception. + if (!PlatformDependent.isWindows()) { + if (IoUringIncubatorTransportFactory.isAvailable() && !config.isUseOnlyEpollNativeTransport()) { + return new IoUringIncubatorTransportFactory(); + } else if (EpollTransportFactory.isAvailable()) { + return new EpollTransportFactory(); + } + } + + throw new IllegalArgumentException("No suitable native transport (Epoll, Io_Uring or KQueue) available"); + } + + public static boolean isSslHandlerConfigured(ChannelPipeline pipeline) { + return pipeline.get(SSL_HANDLER) != null; + } + + private static Bootstrap newBootstrap(ChannelFactory channelFactory, EventLoopGroup eventLoopGroup, AsyncHttpClientConfig config) { + Bootstrap bootstrap = new Bootstrap().channelFactory(channelFactory).group(eventLoopGroup) + .option(ChannelOption.ALLOCATOR, config.getAllocator() != null ? config.getAllocator() : ByteBufAllocator.DEFAULT) + .option(ChannelOption.TCP_NODELAY, config.isTcpNoDelay()) + .option(ChannelOption.SO_REUSEADDR, config.isSoReuseAddress()) + .option(ChannelOption.SO_KEEPALIVE, config.isSoKeepAlive()) + .option(ChannelOption.AUTO_CLOSE, false); + + long connectTimeout = config.getConnectTimeout().toMillis(); + if (connectTimeout > 0) { + connectTimeout = Math.min(connectTimeout, Integer.MAX_VALUE); + bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) connectTimeout); + } + + if (config.getSoLinger() >= 0) { + bootstrap.option(ChannelOption.SO_LINGER, config.getSoLinger()); + } + + if (config.getSoSndBuf() >= 0) { + bootstrap.option(ChannelOption.SO_SNDBUF, config.getSoSndBuf()); + } + + if (config.getSoRcvBuf() >= 0) { + bootstrap.option(ChannelOption.SO_RCVBUF, config.getSoRcvBuf()); + } + + for (Entry, Object> entry : config.getChannelOptions().entrySet()) { + bootstrap.option(entry.getKey(), entry.getValue()); + } + + return bootstrap; + } + + public void configureBootstraps(NettyRequestSender requestSender) { + final AsyncHttpClientHandler httpHandler = new HttpHandler(config, this, requestSender); + wsHandler = new WebSocketHandler(config, this, requestSender); + + httpBootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ChannelPipeline pipeline = ch.pipeline() + .addLast(HTTP_CLIENT_CODEC, newHttpClientCodec()); + + if (config.isEnableAutomaticDecompression()) { + // Add automatic decompression if desired + pipeline = pipeline.addLast(INFLATER_HANDLER, newHttpContentDecompressor()); + } + + pipeline = pipeline + .addLast(CHUNKED_WRITER_HANDLER, new ChunkedWriteHandler()) + .addLast(AHC_HTTP_HANDLER, httpHandler); + + if (LOGGER.isTraceEnabled()) { + pipeline.addFirst(LOGGING_HANDLER, new LoggingHandler(LogLevel.TRACE)); + } + + if (config.getHttpAdditionalChannelInitializer() != null) { + config.getHttpAdditionalChannelInitializer().accept(ch); + } + } + }); + + wsBootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) { + ChannelPipeline pipeline = ch.pipeline() + .addLast(HTTP_CLIENT_CODEC, newHttpClientCodec()) + .addLast(AHC_WS_HANDLER, wsHandler); + + if (config.isEnableWebSocketCompression()) { + pipeline.addBefore(AHC_WS_HANDLER, WS_COMPRESSOR_HANDLER, WebSocketClientCompressionHandler.INSTANCE); + } + + if (LOGGER.isTraceEnabled()) { + pipeline.addFirst(LOGGING_HANDLER, new LoggingHandler(LogLevel.TRACE)); + } + + if (config.getWsAdditionalChannelInitializer() != null) { + config.getWsAdditionalChannelInitializer().accept(ch); + } + } + }); + } + + private HttpContentDecompressor newHttpContentDecompressor() { + if (config.isKeepEncodingHeader()) { + return new HttpContentDecompressor() { + @Override + protected String getTargetContentEncoding(String contentEncoding) { + return contentEncoding; + } + }; + } else { + return new HttpContentDecompressor(); + } + } + + public final void tryToOfferChannelToPool(Channel channel, AsyncHandler asyncHandler, boolean keepAlive, Object partitionKey) { + if (channel.isActive() && keepAlive) { + LOGGER.debug("Adding key: {} for channel {}", partitionKey, channel); + Channels.setDiscard(channel); + + try { + asyncHandler.onConnectionOffer(channel); + } catch (Exception e) { + LOGGER.error("onConnectionOffer crashed", e); + } + + if (!channelPool.offer(channel, partitionKey)) { + // rejected by pool + closeChannel(channel); + } + } else { + // not offered + closeChannel(channel); + } + } + + public Channel poll(Uri uri, String virtualHost, ProxyServer proxy, ChannelPoolPartitioning connectionPoolPartitioning) { + Object partitionKey = connectionPoolPartitioning.getPartitionKey(uri, virtualHost, proxy); + return channelPool.poll(partitionKey); + } + + public void removeAll(Channel connection) { + channelPool.removeAll(connection); + } + + private void doClose() { + ChannelGroupFuture groupFuture = openChannels.close(); + channelPool.destroy(); + groupFuture.addListener(future -> sslEngineFactory.destroy()); + } + + public void close() { + if (allowReleaseEventLoopGroup) { + final long shutdownQuietPeriod = config.getShutdownQuietPeriod().toMillis(); + final long shutdownTimeout = config.getShutdownTimeout().toMillis(); + eventLoopGroup + .shutdownGracefully(shutdownQuietPeriod, shutdownTimeout, TimeUnit.MILLISECONDS) + .addListener(future -> doClose()); + } else { + doClose(); + } + } + + public void closeChannel(Channel channel) { + LOGGER.debug("Closing Channel {} ", channel); + Channels.setDiscard(channel); + removeAll(channel); + Channels.silentlyCloseChannel(channel); + } + + public void registerOpenChannel(Channel channel) { + openChannels.add(channel); + } + + private HttpClientCodec newHttpClientCodec() { + return new HttpClientCodec(// + config.getHttpClientCodecMaxInitialLineLength(), + config.getHttpClientCodecMaxHeaderSize(), + config.getHttpClientCodecMaxChunkSize(), + false, + config.isValidateResponseHeaders(), + config.getHttpClientCodecInitialBufferSize()); + } + + private SslHandler createSslHandler(String peerHost, int peerPort) { + SSLEngine sslEngine = sslEngineFactory.newSslEngine(config, peerHost, peerPort); + SslHandler sslHandler = new SslHandler(sslEngine); + if (handshakeTimeout > 0) { + sslHandler.setHandshakeTimeoutMillis(handshakeTimeout); + } + return sslHandler; + } + + public Future updatePipelineForHttpTunneling(ChannelPipeline pipeline, Uri requestUri) { + Future whenHandshaked = null; + + if (pipeline.get(HTTP_CLIENT_CODEC) != null) { + pipeline.remove(HTTP_CLIENT_CODEC); + } + + if (requestUri.isSecured()) { + if (!isSslHandlerConfigured(pipeline)) { + SslHandler sslHandler = createSslHandler(requestUri.getHost(), requestUri.getExplicitPort()); + whenHandshaked = sslHandler.handshakeFuture(); + pipeline.addBefore(INFLATER_HANDLER, SSL_HANDLER, sslHandler); + } + pipeline.addAfter(SSL_HANDLER, HTTP_CLIENT_CODEC, newHttpClientCodec()); + + } else { + pipeline.addBefore(AHC_HTTP_HANDLER, HTTP_CLIENT_CODEC, newHttpClientCodec()); + } + + if (requestUri.isWebSocket()) { + pipeline.addAfter(AHC_HTTP_HANDLER, AHC_WS_HANDLER, wsHandler); + + if (config.isEnableWebSocketCompression()) { + pipeline.addBefore(AHC_WS_HANDLER, WS_COMPRESSOR_HANDLER, WebSocketClientCompressionHandler.INSTANCE); + } + + pipeline.remove(AHC_HTTP_HANDLER); + } + return whenHandshaked; + } + + public SslHandler addSslHandler(ChannelPipeline pipeline, Uri uri, String virtualHost, boolean hasSocksProxyHandler) { + String peerHost; + int peerPort; + + if (virtualHost != null) { + int i = virtualHost.indexOf(':'); + if (i == -1) { + peerHost = virtualHost; + peerPort = uri.getSchemeDefaultPort(); + } else { + peerHost = virtualHost.substring(0, i); + peerPort = Integer.valueOf(virtualHost.substring(i + 1)); + } + + } else { + peerHost = uri.getHost(); + peerPort = uri.getExplicitPort(); + } + + SslHandler sslHandler = createSslHandler(peerHost, peerPort); + if (hasSocksProxyHandler) { + pipeline.addAfter(SOCKS_HANDLER, SSL_HANDLER, sslHandler); + } else { + pipeline.addFirst(SSL_HANDLER, sslHandler); + } + return sslHandler; + } + + public Future getBootstrap(Uri uri, NameResolver nameResolver, ProxyServer proxy) { + final Promise promise = ImmediateEventExecutor.INSTANCE.newPromise(); + + if (uri.isWebSocket() && proxy == null) { + return promise.setSuccess(wsBootstrap); + } + + if (proxy != null && proxy.getProxyType().isSocks()) { + Bootstrap socksBootstrap = httpBootstrap.clone(); + ChannelHandler httpBootstrapHandler = socksBootstrap.config().handler(); + + nameResolver.resolve(proxy.getHost()).addListener((Future whenProxyAddress) -> { + if (whenProxyAddress.isSuccess()) { + socksBootstrap.handler(new ChannelInitializer() { + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + httpBootstrapHandler.handlerAdded(ctx); + super.handlerAdded(ctx); + } + + @Override + protected void initChannel(Channel channel) throws Exception { + InetSocketAddress proxyAddress = new InetSocketAddress(whenProxyAddress.get(), proxy.getPort()); + Realm realm = proxy.getRealm(); + String username = realm != null ? realm.getPrincipal() : null; + String password = realm != null ? realm.getPassword() : null; + ProxyHandler socksProxyHandler; + switch (proxy.getProxyType()) { + case SOCKS_V4: + socksProxyHandler = new Socks4ProxyHandler(proxyAddress, username); + break; + + case SOCKS_V5: + socksProxyHandler = new Socks5ProxyHandler(proxyAddress, username, password); + break; + + default: + throw new IllegalArgumentException("Only SOCKS4 and SOCKS5 supported at the moment."); + } + channel.pipeline().addFirst(SOCKS_HANDLER, socksProxyHandler); + } + }); + promise.setSuccess(socksBootstrap); + + } else { + promise.setFailure(whenProxyAddress.cause()); + } + }); + + } else { + promise.setSuccess(httpBootstrap); + } + + return promise; + } + + public void upgradePipelineForWebSockets(ChannelPipeline pipeline) { + pipeline.addAfter(HTTP_CLIENT_CODEC, WS_ENCODER_HANDLER, new WebSocket08FrameEncoder(true)); + pipeline.addAfter(WS_ENCODER_HANDLER, WS_DECODER_HANDLER, new WebSocket08FrameDecoder(false, + config.isEnableWebSocketCompression(), config.getWebSocketMaxFrameSize())); + + if (config.isAggregateWebSocketFrameFragments()) { + pipeline.addAfter(WS_DECODER_HANDLER, WS_FRAME_AGGREGATOR, new WebSocketFrameAggregator(config.getWebSocketMaxBufferSize())); + } + + pipeline.remove(HTTP_CLIENT_CODEC); + } + + private OnLastHttpContentCallback newDrainCallback(final NettyResponseFuture future, final Channel channel, final boolean keepAlive, final Object partitionKey) { + return new OnLastHttpContentCallback(future) { + @Override + public void call() { + tryToOfferChannelToPool(channel, future.getAsyncHandler(), keepAlive, partitionKey); + } + }; + } + + public void drainChannelAndOffer(Channel channel, NettyResponseFuture future) { + drainChannelAndOffer(channel, future, future.isKeepAlive(), future.getPartitionKey()); + } + + public void drainChannelAndOffer(Channel channel, NettyResponseFuture future, boolean keepAlive, Object partitionKey) { + Channels.setAttribute(channel, newDrainCallback(future, channel, keepAlive, partitionKey)); + } + + public ChannelPool getChannelPool() { + return channelPool; + } + + public EventLoopGroup getEventLoopGroup() { + return eventLoopGroup; + } + + public ClientStats getClientStats() { + Map totalConnectionsPerHost = openChannels.stream() + .map(Channel::remoteAddress) + .filter(a -> a instanceof InetSocketAddress) + .map(a -> (InetSocketAddress) a) + .map(InetSocketAddress::getHostString) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + + Map idleConnectionsPerHost = channelPool.getIdleChannelCountPerHost(); + + Map statsPerHost = totalConnectionsPerHost.entrySet() + .stream() + .collect(Collectors.toMap(Entry::getKey, entry -> { + final long totalConnectionCount = entry.getValue(); + final long idleConnectionCount = idleConnectionsPerHost.getOrDefault(entry.getKey(), 0L); + final long activeConnectionCount = totalConnectionCount - idleConnectionCount; + return new HostStats(activeConnectionCount, idleConnectionCount); + })); + return new ClientStats(statsPerHost); + } + + public boolean isOpen() { + return channelPool.isOpen(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/ChannelState.java b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelState.java new file mode 100644 index 0000000000..1b550983e6 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelState.java @@ -0,0 +1,38 @@ +/* + * 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.channel; + +public enum ChannelState { + /** + * 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 new file mode 100755 index 0000000000..c56a05ba54 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/Channels.java @@ -0,0 +1,72 @@ +/* + * 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. + * 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.channel; + +import io.netty.channel.Channel; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import org.asynchttpclient.netty.DiscardEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class Channels { + + private static final Logger LOGGER = LoggerFactory.getLogger(Channels.class); + + private static final AttributeKey DEFAULT_ATTRIBUTE = AttributeKey.valueOf("default"); + private static final AttributeKey ACTIVE_TOKEN_ATTRIBUTE = AttributeKey.valueOf("activeToken"); + + private Channels() { + // Prevent outside initialization + } + + public static Object getAttribute(Channel channel) { + Attribute attr = channel.attr(DEFAULT_ATTRIBUTE); + return attr != null ? attr.get() : null; + } + + public static void setAttribute(Channel channel, Object o) { + channel.attr(DEFAULT_ATTRIBUTE).set(o); + } + + public static void setDiscard(Channel channel) { + setAttribute(channel, DiscardEvent.DISCARD); + } + + public static boolean isChannelActive(Channel channel) { + return channel != null && channel.isActive(); + } + + public static void setActiveToken(Channel channel) { + channel.attr(ACTIVE_TOKEN_ATTRIBUTE).set(Active.INSTANCE); + } + + public static boolean isActiveTokenSet(Channel channel) { + return channel != null && channel.attr(ACTIVE_TOKEN_ATTRIBUTE).getAndSet(null) != null; + } + + public static void silentlyCloseChannel(Channel channel) { + try { + if (channel != null && channel.isActive()) { + channel.close(); + } + } catch (Throwable t) { + LOGGER.debug("Failed to close channel", t); + } + } + + private enum Active {INSTANCE} +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/CombinedConnectionSemaphore.java b/client/src/main/java/org/asynchttpclient/netty/channel/CombinedConnectionSemaphore.java new file mode 100644 index 0000000000..36748b077f --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/CombinedConnectionSemaphore.java @@ -0,0 +1,71 @@ +/* + * 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.netty.channel; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * A combined {@link ConnectionSemaphore} with two limits - a global limit and a per-host limit + */ +public class CombinedConnectionSemaphore extends PerHostConnectionSemaphore { + protected final MaxConnectionSemaphore globalMaxConnectionSemaphore; + + CombinedConnectionSemaphore(int maxConnections, int maxConnectionsPerHost, int acquireTimeout) { + super(maxConnectionsPerHost, acquireTimeout); + globalMaxConnectionSemaphore = new MaxConnectionSemaphore(maxConnections, acquireTimeout); + } + + @Override + public void acquireChannelLock(Object partitionKey) throws IOException { + long remainingTime = acquireTimeout > 0 ? acquireGlobalTimed(partitionKey) : acquireGlobal(partitionKey); + + try { + if (remainingTime < 0 || !getFreeConnectionsForHost(partitionKey).tryAcquire(remainingTime, TimeUnit.MILLISECONDS)) { + releaseGlobal(partitionKey); + throw tooManyConnectionsPerHost; + } + } catch (InterruptedException e) { + releaseGlobal(partitionKey); + throw new RuntimeException(e); + } + } + + protected void releaseGlobal(Object partitionKey) { + globalMaxConnectionSemaphore.releaseChannelLock(partitionKey); + } + + protected long acquireGlobal(Object partitionKey) throws IOException { + globalMaxConnectionSemaphore.acquireChannelLock(partitionKey); + return 0; + } + + /* + * Acquires the global lock and returns the remaining time, in millis, to acquire the per-host lock + */ + protected long acquireGlobalTimed(Object partitionKey) throws IOException { + long beforeGlobalAcquire = System.currentTimeMillis(); + acquireGlobal(partitionKey); + long lockTime = System.currentTimeMillis() - beforeGlobalAcquire; + return acquireTimeout - lockTime; + } + + @Override + public void releaseChannelLock(Object partitionKey) { + globalMaxConnectionSemaphore.releaseChannelLock(partitionKey); + super.releaseChannelLock(partitionKey); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/ConnectionSemaphore.java b/client/src/main/java/org/asynchttpclient/netty/channel/ConnectionSemaphore.java new file mode 100644 index 0000000000..300d0a8cd4 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/ConnectionSemaphore.java @@ -0,0 +1,28 @@ +/* + * 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.netty.channel; + +import java.io.IOException; + +/** + * Connections limiter. + */ +public interface ConnectionSemaphore { + + void acquireChannelLock(Object partitionKey) throws IOException; + + void releaseChannelLock(Object partitionKey); +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/ConnectionSemaphoreFactory.java b/client/src/main/java/org/asynchttpclient/netty/channel/ConnectionSemaphoreFactory.java new file mode 100644 index 0000000000..d763f917f6 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/ConnectionSemaphoreFactory.java @@ -0,0 +1,24 @@ +/* + * 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.netty.channel; + +import org.asynchttpclient.AsyncHttpClientConfig; + +@FunctionalInterface +public interface ConnectionSemaphoreFactory { + + ConnectionSemaphore newConnectionSemaphore(AsyncHttpClientConfig config); +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java b/client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java new file mode 100755 index 0000000000..c4042fdfc7 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java @@ -0,0 +1,388 @@ +/* + * 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. + * 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.channel; + +import io.netty.channel.Channel; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.channel.ChannelPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; +import static org.asynchttpclient.util.DateUtils.unpreciseMillisTime; + +/** + * A simple implementation of {@link ChannelPool} based on a {@link ConcurrentHashMap} + */ +public final class DefaultChannelPool implements ChannelPool { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultChannelPool.class); + private static final AttributeKey CHANNEL_CREATION_ATTRIBUTE_KEY = AttributeKey.valueOf("channelCreation"); + + private final ConcurrentHashMap> partitions = new ConcurrentHashMap<>(); + private final AtomicBoolean isClosed = new AtomicBoolean(false); + private final Timer nettyTimer; + private final long connectionTtl; + private final boolean connectionTtlEnabled; + private final long maxIdleTime; + private final boolean maxIdleTimeEnabled; + private final long cleanerPeriod; + private final PoolLeaseStrategy poolLeaseStrategy; + + public DefaultChannelPool(AsyncHttpClientConfig config, Timer hashedWheelTimer) { + this(config.getPooledConnectionIdleTimeout(), + config.getConnectionTtl(), + hashedWheelTimer, + config.getConnectionPoolCleanerPeriod()); + } + + public DefaultChannelPool(Duration maxIdleTime, Duration connectionTtl, Timer nettyTimer, Duration cleanerPeriod) { + this(maxIdleTime, connectionTtl, PoolLeaseStrategy.LIFO, nettyTimer, cleanerPeriod); + } + + public DefaultChannelPool(Duration maxIdleTime, Duration connectionTtl, PoolLeaseStrategy poolLeaseStrategy, Timer nettyTimer, Duration cleanerPeriod) { + final long maxIdleTimeInMs = maxIdleTime.toMillis(); + final long connectionTtlInMs = connectionTtl.toMillis(); + final long cleanerPeriodInMs = cleanerPeriod.toMillis(); + this.maxIdleTime = maxIdleTimeInMs; + this.connectionTtl = connectionTtlInMs; + connectionTtlEnabled = connectionTtlInMs > 0; + this.nettyTimer = nettyTimer; + maxIdleTimeEnabled = maxIdleTimeInMs > 0; + this.poolLeaseStrategy = poolLeaseStrategy; + + this.cleanerPeriod = Math.min(cleanerPeriodInMs, Math.min(connectionTtlEnabled ? connectionTtlInMs : Integer.MAX_VALUE, + maxIdleTimeEnabled ? maxIdleTimeInMs : Integer.MAX_VALUE)); + + if (connectionTtlEnabled || maxIdleTimeEnabled) { + scheduleNewIdleChannelDetector(new IdleChannelDetector()); + } + } + + private void scheduleNewIdleChannelDetector(TimerTask task) { + nettyTimer.newTimeout(task, cleanerPeriod, TimeUnit.MILLISECONDS); + } + + private boolean isTtlExpired(Channel channel, long now) { + if (!connectionTtlEnabled) { + return false; + } + + ChannelCreation creation = channel.attr(CHANNEL_CREATION_ATTRIBUTE_KEY).get(); + return creation != null && now - creation.creationTime >= connectionTtl; + } + + @Override + public boolean offer(Channel channel, Object partitionKey) { + if (isClosed.get()) { + return false; + } + + long now = unpreciseMillisTime(); + + if (isTtlExpired(channel, now)) { + return false; + } + + boolean offered = offer0(channel, partitionKey, now); + if (connectionTtlEnabled && offered) { + registerChannelCreation(channel, partitionKey, now); + } + + return offered; + } + + private boolean offer0(Channel channel, Object partitionKey, long now) { + ConcurrentLinkedDeque partition = partitions.get(partitionKey); + if (partition == null) { + partition = partitions.computeIfAbsent(partitionKey, pk -> new ConcurrentLinkedDeque<>()); + } + return partition.offerFirst(new IdleChannel(channel, now)); + } + + private static void registerChannelCreation(Channel channel, Object partitionKey, long now) { + Attribute channelCreationAttribute = channel.attr(CHANNEL_CREATION_ATTRIBUTE_KEY); + if (channelCreationAttribute.get() == null) { + channelCreationAttribute.set(new ChannelCreation(now, partitionKey)); + } + } + + @Override + public Channel poll(Object partitionKey) { + IdleChannel idleChannel = null; + ConcurrentLinkedDeque partition = partitions.get(partitionKey); + if (partition != null) { + while (idleChannel == null) { + idleChannel = poolLeaseStrategy.lease(partition); + + if (idleChannel == null) + // pool is empty + { + break; + } else if (!Channels.isChannelActive(idleChannel.channel)) { + idleChannel = null; + LOGGER.trace("Channel is inactive, probably remotely closed!"); + } else if (!idleChannel.takeOwnership()) { + idleChannel = null; + LOGGER.trace("Couldn't take ownership of channel, probably in the process of being expired!"); + } + } + } + return idleChannel != null ? idleChannel.channel : null; + } + + @Override + public boolean removeAll(Channel channel) { + ChannelCreation creation = connectionTtlEnabled ? channel.attr(CHANNEL_CREATION_ATTRIBUTE_KEY).get() : null; + return !isClosed.get() && creation != null && partitions.get(creation.partitionKey).remove(new IdleChannel(channel, Long.MIN_VALUE)); + } + + @Override + public boolean isOpen() { + return !isClosed.get(); + } + + @Override + public void destroy() { + if (isClosed.getAndSet(true)) { + return; + } + + partitions.clear(); + } + + private static void close(Channel channel) { + // FIXME pity to have to do this here + Channels.setDiscard(channel); + Channels.silentlyCloseChannel(channel); + } + + private void flushPartition(Object partitionKey, ConcurrentLinkedDeque partition) { + if (partition != null) { + partitions.remove(partitionKey); + for (IdleChannel idleChannel : partition) { + close(idleChannel.channel); + } + } + } + + @Override + public void flushPartitions(Predicate predicate) { + for (Map.Entry> partitionsEntry : partitions.entrySet()) { + Object partitionKey = partitionsEntry.getKey(); + if (predicate.test(partitionKey)) { + flushPartition(partitionKey, partitionsEntry.getValue()); + } + } + } + + @Override + public Map getIdleChannelCountPerHost() { + return partitions + .values() + .stream() + .flatMap(ConcurrentLinkedDeque::stream) + .map(idle -> idle.getChannel().remoteAddress()) + .filter(a -> a.getClass() == InetSocketAddress.class) + .map(a -> (InetSocketAddress) a) + .map(InetSocketAddress::getHostString) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + } + + public enum PoolLeaseStrategy { + LIFO { + @Override + public E lease(Deque d) { + return d.pollFirst(); + } + }, + FIFO { + @Override + public E lease(Deque d) { + return d.pollLast(); + } + }; + + abstract E lease(Deque d); + } + + private static final class ChannelCreation { + final long creationTime; + final Object partitionKey; + + ChannelCreation(long creationTime, Object partitionKey) { + this.creationTime = creationTime; + this.partitionKey = partitionKey; + } + } + + private static final class IdleChannel { + + private static final AtomicIntegerFieldUpdater ownedField = AtomicIntegerFieldUpdater.newUpdater(IdleChannel.class, "owned"); + + final Channel channel; + final long start; + @SuppressWarnings("unused") + private volatile int owned; + + IdleChannel(Channel channel, long start) { + this.channel = requireNonNull(channel, "channel"); + this.start = start; + } + + public boolean takeOwnership() { + return ownedField.getAndSet(this, 1) == 0; + } + + public Channel getChannel() { + return channel; + } + + @Override + // only depends on channel + public boolean equals(Object o) { + return this == o || o instanceof IdleChannel && channel.equals(((IdleChannel) o).channel); + } + + @Override + public int hashCode() { + return channel.hashCode(); + } + } + + private final class IdleChannelDetector implements TimerTask { + + private boolean isIdleTimeoutExpired(IdleChannel idleChannel, long now) { + return maxIdleTimeEnabled && now - idleChannel.start >= maxIdleTime; + } + + private List expiredChannels(ConcurrentLinkedDeque partition, long now) { + // lazy create + List idleTimeoutChannels = null; + for (IdleChannel idleChannel : partition) { + boolean isIdleTimeoutExpired = isIdleTimeoutExpired(idleChannel, now); + boolean isRemotelyClosed = !Channels.isChannelActive(idleChannel.channel); + boolean isTtlExpired = isTtlExpired(idleChannel.channel, now); + if (isIdleTimeoutExpired || isRemotelyClosed || isTtlExpired) { + + LOGGER.debug("Adding Candidate expired Channel {} isIdleTimeoutExpired={} isRemotelyClosed={} isTtlExpired={}", + idleChannel.channel, isIdleTimeoutExpired, isRemotelyClosed, isTtlExpired); + + if (idleTimeoutChannels == null) { + idleTimeoutChannels = new ArrayList<>(1); + } + idleTimeoutChannels.add(idleChannel); + } + } + + return idleTimeoutChannels != null ? idleTimeoutChannels : Collections.emptyList(); + } + + private List closeChannels(List candidates) { + // lazy create, only if we hit a non-closeable channel + List closedChannels = null; + for (int i = 0; i < candidates.size(); i++) { + // We call takeOwnership here to avoid closing a channel that has just been taken out + // of the pool, otherwise we risk closing an active connection. + IdleChannel idleChannel = candidates.get(i); + if (idleChannel.takeOwnership()) { + LOGGER.debug("Closing Idle Channel {}", idleChannel.channel); + close(idleChannel.channel); + if (closedChannels != null) { + closedChannels.add(idleChannel); + } + + } else if (closedChannels == null) { + // first non-closeable to be skipped, copy all + // previously skipped closeable channels + closedChannels = new ArrayList<>(candidates.size()); + for (int j = 0; j < i; j++) { + closedChannels.add(candidates.get(j)); + } + } + } + + return closedChannels != null ? closedChannels : candidates; + } + + @Override + public void run(Timeout timeout) { + + if (isClosed.get()) { + return; + } + + if (LOGGER.isDebugEnabled()) { + for (Map.Entry> entry : partitions.entrySet()) { + int size = entry.getValue().size(); + if (size > 0) { + LOGGER.debug("Entry count for : {} : {}", entry.getKey(), size); + } + } + } + + long start = unpreciseMillisTime(); + int closedCount = 0; + int totalCount = 0; + + for (ConcurrentLinkedDeque partition : partitions.values()) { + + // store in intermediate unsynchronized lists to minimize + // the impact on the ConcurrentLinkedDeque + if (LOGGER.isDebugEnabled()) { + totalCount += partition.size(); + } + + List closedChannels = closeChannels(expiredChannels(partition, start)); + + if (!closedChannels.isEmpty()) { + partition.removeAll(closedChannels); + closedCount += closedChannels.size(); + } + } + + if (LOGGER.isDebugEnabled()) { + long duration = unpreciseMillisTime() - start; + if (closedCount > 0) { + LOGGER.debug("Closed {} connections out of {} in {} ms", closedCount, totalCount, duration); + } + } + + scheduleNewIdleChannelDetector(timeout.task()); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/DefaultConnectionSemaphoreFactory.java b/client/src/main/java/org/asynchttpclient/netty/channel/DefaultConnectionSemaphoreFactory.java new file mode 100644 index 0000000000..cbe5c046e6 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/DefaultConnectionSemaphoreFactory.java @@ -0,0 +1,40 @@ +/* + * 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.netty.channel; + +import org.asynchttpclient.AsyncHttpClientConfig; + +public class DefaultConnectionSemaphoreFactory implements ConnectionSemaphoreFactory { + + @Override + public ConnectionSemaphore newConnectionSemaphore(AsyncHttpClientConfig config) { + int acquireFreeChannelTimeout = Math.max(0, config.getAcquireFreeChannelTimeout()); + int maxConnections = config.getMaxConnections(); + int maxConnectionsPerHost = config.getMaxConnectionsPerHost(); + + if (maxConnections > 0 && maxConnectionsPerHost > 0) { + return new CombinedConnectionSemaphore(maxConnections, maxConnectionsPerHost, acquireFreeChannelTimeout); + } + if (maxConnections > 0) { + return new MaxConnectionSemaphore(maxConnections, acquireFreeChannelTimeout); + } + if (maxConnectionsPerHost > 0) { + return new CombinedConnectionSemaphore(maxConnections, maxConnectionsPerHost, acquireFreeChannelTimeout); + } + + return new NoopConnectionSemaphore(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/EpollTransportFactory.java b/client/src/main/java/org/asynchttpclient/netty/channel/EpollTransportFactory.java new file mode 100644 index 0000000000..d24b32b706 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/EpollTransportFactory.java @@ -0,0 +1,44 @@ +/* + * 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.netty.channel; + +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.epoll.EpollSocketChannel; + +import java.util.concurrent.ThreadFactory; + +class EpollTransportFactory implements TransportFactory { + + static boolean isAvailable() { + try { + Class.forName("io.netty.channel.epoll.Epoll"); + } catch (ClassNotFoundException e) { + return false; + } + return Epoll.isAvailable(); + } + + @Override + public EpollSocketChannel newChannel() { + return new EpollSocketChannel(); + } + + @Override + public EpollEventLoopGroup newEventLoopGroup(int ioThreadsCount, ThreadFactory threadFactory) { + return new EpollEventLoopGroup(ioThreadsCount, threadFactory); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/InfiniteSemaphore.java b/client/src/main/java/org/asynchttpclient/netty/channel/InfiniteSemaphore.java new file mode 100644 index 0000000000..8d7cbdc3d9 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/InfiniteSemaphore.java @@ -0,0 +1,111 @@ +/* + * 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.netty.channel; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +/** + * A java.util.concurrent.Semaphore that always has Integer.Integer.MAX_VALUE free permits + * + * @author Alex Maltinsky + */ +public class InfiniteSemaphore extends Semaphore { + + public static final InfiniteSemaphore INSTANCE = new InfiniteSemaphore(); + private static final long serialVersionUID = 1L; + + private InfiniteSemaphore() { + super(Integer.MAX_VALUE); + } + + @Override + public void acquire() { + // NO-OP + } + + @Override + public void acquireUninterruptibly() { + // NO-OP + } + + @Override + public boolean tryAcquire() { + return true; + } + + @Override + public boolean tryAcquire(long timeout, TimeUnit unit) { + return true; + } + + @Override + public void release() { + // NO-OP + } + + @Override + public void acquire(int permits) { + // NO-OP + } + + @Override + public void acquireUninterruptibly(int permits) { + // NO-OP + } + + @Override + public boolean tryAcquire(int permits) { + return true; + } + + @Override + public boolean tryAcquire(int permits, long timeout, TimeUnit unit) { + return true; + } + + @Override + public void release(int permits) { + // NO-OP + } + + @Override + public int availablePermits() { + return Integer.MAX_VALUE; + } + + @Override + public int drainPermits() { + return Integer.MAX_VALUE; + } + + @Override + protected void reducePermits(int reduction) { + // NO-OP + } + + @Override + public boolean isFair() { + return true; + } + + @Override + protected Collection getQueuedThreads() { + return Collections.emptyList(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/IoUringIncubatorTransportFactory.java b/client/src/main/java/org/asynchttpclient/netty/channel/IoUringIncubatorTransportFactory.java new file mode 100644 index 0000000000..2065ef10b8 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/IoUringIncubatorTransportFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022-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.channel; + +import io.netty.incubator.channel.uring.IOUring; +import io.netty.incubator.channel.uring.IOUringEventLoopGroup; +import io.netty.incubator.channel.uring.IOUringSocketChannel; + +import java.util.concurrent.ThreadFactory; + +class IoUringIncubatorTransportFactory implements TransportFactory { + + static boolean isAvailable() { + try { + Class.forName("io.netty.incubator.channel.uring.IOUring"); + } catch (ClassNotFoundException e) { + return false; + } + return IOUring.isAvailable(); + } + + @Override + public IOUringSocketChannel newChannel() { + return new IOUringSocketChannel(); + } + + @Override + public IOUringEventLoopGroup newEventLoopGroup(int ioThreadsCount, ThreadFactory threadFactory) { + return new IOUringEventLoopGroup(ioThreadsCount, threadFactory); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/KQueueTransportFactory.java b/client/src/main/java/org/asynchttpclient/netty/channel/KQueueTransportFactory.java new file mode 100644 index 0000000000..54bcfe0d48 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/KQueueTransportFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-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.channel; + +import io.netty.channel.kqueue.KQueue; +import io.netty.channel.kqueue.KQueueEventLoopGroup; +import io.netty.channel.kqueue.KQueueSocketChannel; + +import java.util.concurrent.ThreadFactory; + +class KQueueTransportFactory implements TransportFactory { + + static boolean isAvailable() { + try { + Class.forName("io.netty.channel.kqueue.KQueue"); + } catch (ClassNotFoundException e) { + return false; + } + return KQueue.isAvailable(); + } + + @Override + public KQueueSocketChannel newChannel() { + return new KQueueSocketChannel(); + } + + @Override + public KQueueEventLoopGroup newEventLoopGroup(int ioThreadsCount, ThreadFactory threadFactory) { + return new KQueueEventLoopGroup(ioThreadsCount, threadFactory); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/MaxConnectionSemaphore.java b/client/src/main/java/org/asynchttpclient/netty/channel/MaxConnectionSemaphore.java new file mode 100644 index 0000000000..7640b0e1fa --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/MaxConnectionSemaphore.java @@ -0,0 +1,59 @@ +/* + * 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.netty.channel; + +import org.asynchttpclient.exception.TooManyConnectionsException; + +import java.io.IOException; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace; + +/** + * Max connections limiter. + * + * @author Stepan Koltsov + * @author Alex Maltinsky + */ +public class MaxConnectionSemaphore implements ConnectionSemaphore { + + protected final Semaphore freeChannels; + protected final IOException tooManyConnections; + protected final int acquireTimeout; + + MaxConnectionSemaphore(int maxConnections, int acquireTimeout) { + tooManyConnections = unknownStackTrace(new TooManyConnectionsException(maxConnections), MaxConnectionSemaphore.class, "acquireChannelLock"); + freeChannels = maxConnections > 0 ? new Semaphore(maxConnections) : InfiniteSemaphore.INSTANCE; + this.acquireTimeout = Math.max(0, acquireTimeout); + } + + @Override + public void acquireChannelLock(Object partitionKey) throws IOException { + try { + if (!freeChannels.tryAcquire(acquireTimeout, TimeUnit.MILLISECONDS)) { + throw tooManyConnections; + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + @Override + public void releaseChannelLock(Object partitionKey) { + freeChannels.release(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/NettyChannelConnector.java b/client/src/main/java/org/asynchttpclient/netty/channel/NettyChannelConnector.java new file mode 100644 index 0000000000..3f5da5d7b7 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/NettyChannelConnector.java @@ -0,0 +1,112 @@ +/* + * 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.channel; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHttpClientState; +import org.asynchttpclient.netty.SimpleChannelFutureListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +public class NettyChannelConnector { + + private static final Logger LOGGER = LoggerFactory.getLogger(NettyChannelConnector.class); + + private static final AtomicIntegerFieldUpdater I_UPDATER = AtomicIntegerFieldUpdater + .newUpdater(NettyChannelConnector.class, "i"); + + private final AsyncHandler asyncHandler; + private final InetSocketAddress localAddress; + private final List remoteAddresses; + private final AsyncHttpClientState clientState; + private volatile int i; + + public NettyChannelConnector(InetAddress localAddress, List remoteAddresses, AsyncHandler asyncHandler, AsyncHttpClientState clientState) { + this.localAddress = localAddress != null ? new InetSocketAddress(localAddress, 0) : null; + this.remoteAddresses = remoteAddresses; + this.asyncHandler = asyncHandler; + this.clientState = clientState; + } + + private boolean pickNextRemoteAddress() { + I_UPDATER.incrementAndGet(this); + return i < remoteAddresses.size(); + } + + public void connect(final Bootstrap bootstrap, final NettyConnectListener connectListener) { + final InetSocketAddress remoteAddress = remoteAddresses.get(i); + + try { + asyncHandler.onTcpConnectAttempt(remoteAddress); + } catch (Exception e) { + LOGGER.error("onTcpConnectAttempt crashed", e); + connectListener.onFailure(null, e); + return; + } + + try { + connect0(bootstrap, connectListener, remoteAddress); + } catch (RejectedExecutionException e) { + if (clientState.isClosed()) { + LOGGER.info("Connect crash but engine is shutting down"); + } else { + connectListener.onFailure(null, e); + } + } + } + + private void connect0(Bootstrap bootstrap, final NettyConnectListener connectListener, InetSocketAddress remoteAddress) { + bootstrap.connect(remoteAddress, localAddress) + .addListener(new SimpleChannelFutureListener() { + @Override + public void onSuccess(Channel channel) { + try { + asyncHandler.onTcpConnectSuccess(remoteAddress, channel); + } catch (Exception e) { + LOGGER.error("onTcpConnectSuccess crashed", e); + connectListener.onFailure(channel, e); + return; + } + connectListener.onSuccess(channel, remoteAddress); + } + + @Override + public void onFailure(Channel channel, Throwable t) { + try { + asyncHandler.onTcpConnectFailure(remoteAddress, t); + } catch (Exception e) { + LOGGER.error("onTcpConnectFailure crashed", e); + connectListener.onFailure(channel, e); + return; + } + boolean retry = pickNextRemoteAddress(); + if (retry) { + connect(bootstrap, connectListener); + } else { + connectListener.onFailure(channel, t); + } + } + }); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java b/client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java new file mode 100755 index 0000000000..719733f8ae --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java @@ -0,0 +1,177 @@ +/* + * 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. + * 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.channel; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.ssl.SslHandler; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.Request; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.SimpleFutureListener; +import org.asynchttpclient.netty.future.StackTraceInspector; +import org.asynchttpclient.netty.request.NettyRequestSender; +import org.asynchttpclient.netty.timeout.TimeoutsHolder; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.uri.Uri; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.ConnectException; +import java.net.InetSocketAddress; + +/** + * Non Blocking connect. + */ +public final class NettyConnectListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(NettyConnectListener.class); + + private final NettyRequestSender requestSender; + private final NettyResponseFuture future; + private final ChannelManager channelManager; + private final ConnectionSemaphore connectionSemaphore; + + public NettyConnectListener(NettyResponseFuture future, NettyRequestSender requestSender, ChannelManager channelManager, ConnectionSemaphore connectionSemaphore) { + this.future = future; + this.requestSender = requestSender; + this.channelManager = channelManager; + this.connectionSemaphore = connectionSemaphore; + } + + private boolean futureIsAlreadyCancelled(Channel channel) { + // If Future is cancelled then we will close the channel silently + if (future.isCancelled()) { + Channels.silentlyCloseChannel(channel); + return true; + } + return false; + } + + private void writeRequest(Channel channel) { + if (futureIsAlreadyCancelled(channel)) { + return; + } + + if (LOGGER.isDebugEnabled()) { + HttpRequest httpRequest = future.getNettyRequest().getHttpRequest(); + LOGGER.debug("Using new Channel '{}' for '{}' to '{}'", channel, httpRequest.method(), httpRequest.uri()); + } + + Channels.setAttribute(channel, future); + + channelManager.registerOpenChannel(channel); + future.attachChannel(channel, false); + requestSender.writeRequest(future, channel); + } + + public void onSuccess(Channel channel, InetSocketAddress remoteAddress) { + if (connectionSemaphore != null) { + // transfer lock from future to channel + Object partitionKeyLock = future.takePartitionKeyLock(); + + if (partitionKeyLock != null) { + channel.closeFuture().addListener(future -> connectionSemaphore.releaseChannelLock(partitionKeyLock)); + } + } + + Channels.setActiveToken(channel); + TimeoutsHolder timeoutsHolder = future.getTimeoutsHolder(); + + if (futureIsAlreadyCancelled(channel)) { + return; + } + + Request request = future.getTargetRequest(); + Uri uri = request.getUri(); + timeoutsHolder.setResolvedRemoteAddress(remoteAddress); + ProxyServer proxyServer = future.getProxyServer(); + + // in case of proxy tunneling, we'll add the SslHandler later, after the CONNECT request + if ((proxyServer == null || proxyServer.getProxyType().isSocks()) && uri.isSecured()) { + SslHandler sslHandler; + try { + sslHandler = channelManager.addSslHandler(channel.pipeline(), uri, request.getVirtualHost(), proxyServer != null); + } catch (Exception sslError) { + onFailure(channel, sslError); + return; + } + + final AsyncHandler asyncHandler = future.getAsyncHandler(); + + try { + asyncHandler.onTlsHandshakeAttempt(); + } catch (Exception e) { + LOGGER.error("onTlsHandshakeAttempt crashed", e); + onFailure(channel, e); + return; + } + + sslHandler.handshakeFuture().addListener(new SimpleFutureListener() { + @Override + protected void onSuccess(Channel value) { + try { + asyncHandler.onTlsHandshakeSuccess(sslHandler.engine().getSession()); + } catch (Exception e) { + LOGGER.error("onTlsHandshakeSuccess crashed", e); + NettyConnectListener.this.onFailure(channel, e); + return; + } + writeRequest(channel); + } + + @Override + protected void onFailure(Throwable cause) { + try { + asyncHandler.onTlsHandshakeFailure(cause); + } catch (Exception e) { + LOGGER.error("onTlsHandshakeFailure crashed", e); + NettyConnectListener.this.onFailure(channel, e); + return; + } + NettyConnectListener.this.onFailure(channel, cause); + } + }); + + } else { + writeRequest(channel); + } + } + + public void onFailure(Channel channel, Throwable cause) { + + // beware, channel can be null + Channels.silentlyCloseChannel(channel); + + boolean canRetry = future.incrementRetryAndCheck(); + LOGGER.debug("Trying to recover from failing to connect channel {} with a retry value of {} ", channel, canRetry); + if (canRetry// + && cause != null // FIXME when can we have a null cause? + && (future.getChannelState() != ChannelState.NEW || StackTraceInspector.recoverOnNettyDisconnectException(cause))) { + + if (requestSender.retry(future)) { + return; + } + } + + LOGGER.debug("Failed to recover from connect exception: {} with channel {}", cause, channel); + + String message = cause.getMessage() != null ? cause.getMessage() : future.getUri().getBaseUrl(); + ConnectException e = new ConnectException(message); + e.initCause(cause); + future.abort(e); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/NioTransportFactory.java b/client/src/main/java/org/asynchttpclient/netty/channel/NioTransportFactory.java new file mode 100644 index 0000000000..96eeb37509 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/NioTransportFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019-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.channel; + +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; + +import java.util.concurrent.ThreadFactory; + +enum NioTransportFactory implements TransportFactory { + + INSTANCE; + + @Override + public NioSocketChannel newChannel() { + return new NioSocketChannel(); + } + + @Override + public NioEventLoopGroup newEventLoopGroup(int ioThreadsCount, ThreadFactory threadFactory) { + return new NioEventLoopGroup(ioThreadsCount, threadFactory); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/NoopConnectionSemaphore.java b/client/src/main/java/org/asynchttpclient/netty/channel/NoopConnectionSemaphore.java new file mode 100644 index 0000000000..40afe12be5 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/NoopConnectionSemaphore.java @@ -0,0 +1,32 @@ +/* + * 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.netty.channel; + +import java.io.IOException; + +/** + * No-op implementation of {@link ConnectionSemaphore}. + */ +public class NoopConnectionSemaphore implements ConnectionSemaphore { + + @Override + public void acquireChannelLock(Object partitionKey) throws IOException { + } + + @Override + public void releaseChannelLock(Object partitionKey) { + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/PerHostConnectionSemaphore.java b/client/src/main/java/org/asynchttpclient/netty/channel/PerHostConnectionSemaphore.java new file mode 100644 index 0000000000..5930c0e959 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/PerHostConnectionSemaphore.java @@ -0,0 +1,65 @@ +/* + * 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.netty.channel; + +import org.asynchttpclient.exception.TooManyConnectionsPerHostException; + +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace; + +/** + * Max per-host connections limiter. + */ +public class PerHostConnectionSemaphore implements ConnectionSemaphore { + + protected final ConcurrentHashMap freeChannelsPerHost = new ConcurrentHashMap<>(); + protected final int maxConnectionsPerHost; + protected final IOException tooManyConnectionsPerHost; + protected final int acquireTimeout; + + PerHostConnectionSemaphore(int maxConnectionsPerHost, int acquireTimeout) { + tooManyConnectionsPerHost = unknownStackTrace(new TooManyConnectionsPerHostException(maxConnectionsPerHost), + PerHostConnectionSemaphore.class, "acquireChannelLock"); + this.maxConnectionsPerHost = maxConnectionsPerHost; + this.acquireTimeout = Math.max(0, acquireTimeout); + } + + @Override + public void acquireChannelLock(Object partitionKey) throws IOException { + try { + if (!getFreeConnectionsForHost(partitionKey).tryAcquire(acquireTimeout, TimeUnit.MILLISECONDS)) { + throw tooManyConnectionsPerHost; + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + @Override + public void releaseChannelLock(Object partitionKey) { + getFreeConnectionsForHost(partitionKey).release(); + } + + protected Semaphore getFreeConnectionsForHost(Object partitionKey) { + return maxConnectionsPerHost > 0 ? + freeChannelsPerHost.computeIfAbsent(partitionKey, pk -> new Semaphore(maxConnectionsPerHost)) : + InfiniteSemaphore.INSTANCE; + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/TransportFactory.java b/client/src/main/java/org/asynchttpclient/netty/channel/TransportFactory.java new file mode 100644 index 0000000000..e833fdecf9 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/channel/TransportFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019-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.channel; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFactory; +import io.netty.channel.EventLoopGroup; + +import java.util.concurrent.ThreadFactory; + +public interface TransportFactory extends ChannelFactory { + + L newEventLoopGroup(int ioThreadsCount, ThreadFactory threadFactory); +} diff --git a/client/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.java b/client/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.java new file mode 100755 index 0000000000..28a0f359de --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.java @@ -0,0 +1,80 @@ +/* + * 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. + * 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.future; + +import java.io.IOException; +import java.nio.channels.ClosedChannelException; + +public final class StackTraceInspector { + + private StackTraceInspector() { + // Prevent outside initialization + } + + private static boolean exceptionInMethod(Throwable t, String className, String methodName) { + try { + for (StackTraceElement element : t.getStackTrace()) { + if (element.getClassName().equals(className) && element.getMethodName().equals(methodName)) { + return true; + } + } + } catch (Throwable ignore) { + } + return false; + } + + private static boolean recoverOnConnectCloseException(Throwable t) { + while (true) { + if (exceptionInMethod(t, "sun.nio.ch.SocketChannelImpl", "checkConnect")) { + return true; + } + if (t.getCause() == null) { + return false; + } + t = t.getCause(); + } + } + + public static boolean recoverOnNettyDisconnectException(Throwable t) { + return t instanceof ClosedChannelException + || exceptionInMethod(t, "io.netty.handler.ssl.SslHandler", "disconnect") + || t.getCause() != null && recoverOnConnectCloseException(t.getCause()); + } + + public static boolean recoverOnReadOrWriteException(Throwable t) { + while (true) { + if (t instanceof IOException && "Connection reset by peer".equalsIgnoreCase(t.getMessage())) { + return true; + } + + try { + for (StackTraceElement element : t.getStackTrace()) { + String className = element.getClassName(); + String methodName = element.getMethodName(); + if ("sun.nio.ch.SocketDispatcher".equals(className) && ("read".equals(methodName) || "write".equals(methodName))) { + return true; + } + } + } catch (Throwable ignore) { + } + + 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 new file mode 100755 index 0000000000..aeecbef553 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/handler/AsyncHttpClientHandler.java @@ -0,0 +1,206 @@ +/* + * 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. + * 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.handler; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.PrematureChannelClosureException; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.util.ReferenceCountUtil; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.exception.ChannelClosedException; +import org.asynchttpclient.netty.DiscardEvent; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.OnLastHttpContentCallback; +import org.asynchttpclient.netty.channel.ChannelManager; +import org.asynchttpclient.netty.channel.Channels; +import org.asynchttpclient.netty.future.StackTraceInspector; +import org.asynchttpclient.netty.handler.intercept.Interceptors; +import org.asynchttpclient.netty.request.NettyRequestSender; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.channels.ClosedChannelException; + +import static org.asynchttpclient.util.MiscUtils.getCause; + +public abstract class AsyncHttpClientHandler extends ChannelInboundHandlerAdapter { + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + protected final AsyncHttpClientConfig config; + protected final ChannelManager channelManager; + protected final NettyRequestSender requestSender; + final Interceptors interceptors; + final boolean hasIOExceptionFilters; + + AsyncHttpClientHandler(AsyncHttpClientConfig config, + ChannelManager channelManager, + NettyRequestSender requestSender) { + this.config = config; + this.channelManager = channelManager; + this.requestSender = requestSender; + interceptors = new Interceptors(config, channelManager, requestSender); + hasIOExceptionFilters = !config.getIoExceptionFilters().isEmpty(); + } + + @Override + public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { + Channel channel = ctx.channel(); + Object attribute = Channels.getAttribute(channel); + + try { + if (attribute instanceof OnLastHttpContentCallback) { + if (msg instanceof LastHttpContent) { + ((OnLastHttpContentCallback) attribute).call(); + } + } else if (attribute instanceof NettyResponseFuture) { + NettyResponseFuture future = (NettyResponseFuture) attribute; + future.touch(); + handleRead(channel, future, msg); + } else if (attribute != DiscardEvent.DISCARD) { + // unhandled message + logger.debug("Orphan channel {} with attribute {} received message {}, closing", channel, attribute, msg); + Channels.silentlyCloseChannel(channel); + } + } finally { + ReferenceCountUtil.release(msg); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + if (requestSender.isClosed()) { + return; + } + + Channel channel = ctx.channel(); + channelManager.removeAll(channel); + + Object attribute = Channels.getAttribute(channel); + logger.debug("Channel Closed: {} with attribute {}", channel, attribute); + if (attribute instanceof OnLastHttpContentCallback) { + OnLastHttpContentCallback callback = (OnLastHttpContentCallback) attribute; + Channels.setAttribute(channel, callback.future()); + callback.call(); + + } else if (attribute instanceof NettyResponseFuture) { + NettyResponseFuture future = (NettyResponseFuture) attribute; + future.touch(); + + if (hasIOExceptionFilters && requestSender.applyIoExceptionFiltersAndReplayRequest(future, ChannelClosedException.INSTANCE, channel)) { + return; + } + + handleChannelInactive(future); + requestSender.handleUnexpectedClosedChannel(channel, future); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) { + Throwable cause = getCause(e); + + if (cause instanceof PrematureChannelClosureException || cause instanceof ClosedChannelException) { + return; + } + + Channel channel = ctx.channel(); + NettyResponseFuture future = null; + + logger.debug("Unexpected I/O exception on channel {}", channel, cause); + + try { + Object attribute = Channels.getAttribute(channel); + if (attribute instanceof NettyResponseFuture) { + future = (NettyResponseFuture) attribute; + future.attachChannel(null, false); + future.touch(); + + if (cause instanceof IOException) { + // FIXME why drop the original exception and throw a new one? + if (hasIOExceptionFilters) { + if (!requestSender.applyIoExceptionFiltersAndReplayRequest(future, ChannelClosedException.INSTANCE, channel)) { + // Close the channel so the recovering can occurs. + Channels.silentlyCloseChannel(channel); + } + return; + } + } + + if (StackTraceInspector.recoverOnReadOrWriteException(cause)) { + logger.debug("Trying to recover from dead Channel: {}", channel); + future.pendingException = cause; + return; + } + } else if (attribute instanceof OnLastHttpContentCallback) { + future = ((OnLastHttpContentCallback) attribute).future(); + } + } catch (Throwable t) { + cause = t; + } + + if (future != null) { + try { + logger.debug("Was unable to recover Future: {}", future); + requestSender.abort(channel, future, cause); + handleException(future, e); + } catch (Throwable t) { + logger.error(t.getMessage(), t); + } + } + + channelManager.closeChannel(channel); + // FIXME not really sure + // ctx.fireChannelRead(e); + Channels.silentlyCloseChannel(channel); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + ctx.read(); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + ctx.read(); + } + + void finishUpdate(NettyResponseFuture future, Channel channel, boolean close) { + future.cancelTimeouts(); + + if (close) { + channelManager.closeChannel(channel); + } else { + channelManager.tryToOfferChannelToPool(channel, future.getAsyncHandler(), true, future.getPartitionKey()); + } + + try { + future.done(); + } catch (Exception t) { + // Never propagate exception once we know we are done. + logger.debug(t.getMessage(), t); + } + } + + public abstract void handleRead(Channel channel, NettyResponseFuture future, Object message) throws Exception; + + public abstract void handleException(NettyResponseFuture future, Throwable error); + + public abstract void handleChannelInactive(NettyResponseFuture future); +} diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java b/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java new file mode 100755 index 0000000000..99a23c7e96 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/handler/HttpHandler.java @@ -0,0 +1,156 @@ +/* + * 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. + * 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.handler; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler.Sharable; +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; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHandler.State; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.netty.NettyResponseFuture; +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; + +@Sharable +public final class HttpHandler extends AsyncHttpClientHandler { + + public HttpHandler(AsyncHttpClientConfig config, ChannelManager channelManager, NettyRequestSender requestSender) { + super(config, channelManager, requestSender); + } + + 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 { + return !responseHeaders.isEmpty() && handler.onHeadersReceived(responseHeaders) == State.ABORT; + } + + private void handleHttpResponse(final HttpResponse response, final Channel channel, final NettyResponseFuture future, AsyncHandler handler) throws Exception { + HttpRequest httpRequest = future.getNettyRequest().getHttpRequest(); + logger.debug("\n\nRequest {}\n\nResponse {}\n", httpRequest, response); + + future.setKeepAlive(config.getKeepAliveStrategy().keepAlive((InetSocketAddress) channel.remoteAddress(), future.getTargetRequest(), httpRequest, response)); + + NettyResponseStatus status = new NettyResponseStatus(future.getUri(), response, channel); + HttpHeaders responseHeaders = response.headers(); + + if (!interceptors.exitAfterIntercept(channel, future, handler, response, status, responseHeaders)) { + boolean abort = abortAfterHandlingStatus(handler, httpRequest.method(), status) || abortAfterHandlingHeaders(handler, responseHeaders); + if (abort) { + finishUpdate(future, channel, true); + } + } + } + + private void handleChunk(HttpContent chunk, final Channel channel, final NettyResponseFuture future, AsyncHandler handler) throws Exception { + boolean abort = false; + boolean last = chunk instanceof LastHttpContent; + + // Netty 4: the last chunk is not empty + if (last) { + LastHttpContent lastChunk = (LastHttpContent) chunk; + HttpHeaders trailingHeaders = lastChunk.trailingHeaders(); + if (!trailingHeaders.isEmpty()) { + abort = handler.onTrailingHeadersReceived(trailingHeaders) == State.ABORT; + } + } + + ByteBuf buf = chunk.content(); + if (!abort && (buf.isReadable() || last)) { + HttpResponseBodyPart bodyPart = config.getResponseBodyPartFactory().newResponseBodyPart(buf, last); + abort = handler.onBodyPartReceived(bodyPart) == State.ABORT; + } + + if (abort || last) { + boolean close = abort || !future.isKeepAlive(); + finishUpdate(future, channel, close); + } + } + + @Override + public void handleRead(final Channel channel, final NettyResponseFuture future, final Object e) throws Exception { + // future is already done because of an exception or a timeout + if (future.isDone()) { + // FIXME isn't the channel already properly closed? + channelManager.closeChannel(channel); + return; + } + + AsyncHandler handler = future.getAsyncHandler(); + try { + if (e instanceof DecoderResultProvider) { + DecoderResultProvider object = (DecoderResultProvider) e; + Throwable t = object.decoderResult().cause(); + if (t != null) { + readFailed(channel, future, t); + return; + } + } + + if (e instanceof HttpResponse) { + handleHttpResponse((HttpResponse) e, channel, future, handler); + + } else if (e instanceof HttpContent) { + handleChunk((HttpContent) e, channel, future, handler); + } + } catch (Exception t) { + // e.g. an IOException when trying to open a connection and send the + // next request + if (hasIOExceptionFilters && t instanceof IOException && requestSender.applyIoExceptionFiltersAndReplayRequest(future, (IOException) t, channel)) { + return; + } + + readFailed(channel, future, t); + throw t; + } + } + + private void readFailed(Channel channel, NettyResponseFuture future, Throwable t) { + try { + requestSender.abort(channel, future, t); + } catch (Exception abortException) { + logger.debug("Abort failed", abortException); + } finally { + finishUpdate(future, channel, true); + } + } + + @Override + public void handleException(NettyResponseFuture future, Throwable error) { + } + + @Override + public void handleChannelInactive(NettyResponseFuture future) { + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/WebSocketHandler.java b/client/src/main/java/org/asynchttpclient/netty/handler/WebSocketHandler.java new file mode 100755 index 0000000000..1cf19d0ef1 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/handler/WebSocketHandler.java @@ -0,0 +1,173 @@ +/* + * 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. + * 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.handler; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import org.asynchttpclient.AsyncHandler.State; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.NettyResponseStatus; +import org.asynchttpclient.netty.channel.ChannelManager; +import org.asynchttpclient.netty.channel.Channels; +import org.asynchttpclient.netty.request.NettyRequestSender; +import org.asynchttpclient.netty.ws.NettyWebSocket; +import org.asynchttpclient.ws.WebSocketUpgradeHandler; + +import java.io.IOException; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; +import static io.netty.handler.codec.http.HttpHeaderNames.SEC_WEBSOCKET_ACCEPT; +import static io.netty.handler.codec.http.HttpHeaderNames.SEC_WEBSOCKET_KEY; +import static io.netty.handler.codec.http.HttpHeaderNames.UPGRADE; +import static io.netty.handler.codec.http.HttpResponseStatus.SWITCHING_PROTOCOLS; +import static org.asynchttpclient.ws.WebSocketUtils.getAcceptKey; + +@Sharable +public final class WebSocketHandler extends AsyncHttpClientHandler { + + public WebSocketHandler(AsyncHttpClientConfig config, ChannelManager channelManager, NettyRequestSender requestSender) { + super(config, channelManager, requestSender); + } + + private static WebSocketUpgradeHandler getWebSocketUpgradeHandler(NettyResponseFuture future) { + return (WebSocketUpgradeHandler) future.getAsyncHandler(); + } + + private static NettyWebSocket getNettyWebSocket(NettyResponseFuture future) throws Exception { + return getWebSocketUpgradeHandler(future).onCompleted(); + } + + private void upgrade(Channel channel, NettyResponseFuture future, WebSocketUpgradeHandler handler, HttpResponse response, HttpHeaders responseHeaders) throws Exception { + boolean validStatus = response.status().equals(SWITCHING_PROTOCOLS); + boolean validUpgrade = response.headers().get(UPGRADE) != null; + String connection = response.headers().get(CONNECTION); + boolean validConnection = HttpHeaderValues.UPGRADE.contentEqualsIgnoreCase(connection); + final boolean headerOK = handler.onHeadersReceived(responseHeaders) == State.CONTINUE; + if (!headerOK || !validStatus || !validUpgrade || !validConnection) { + requestSender.abort(channel, future, new IOException("Invalid handshake response")); + return; + } + + String accept = response.headers().get(SEC_WEBSOCKET_ACCEPT); + String key = getAcceptKey(future.getNettyRequest().getHttpRequest().headers().get(SEC_WEBSOCKET_KEY)); + if (accept == null || !accept.equals(key)) { + requestSender.abort(channel, future, new IOException("Invalid challenge. Actual: " + accept + ". Expected: " + key)); + } + + // set back the future so the protocol gets notified of frames + // removing the HttpClientCodec from the pipeline might trigger a read with a WebSocket message + // if it comes in the same frame as the HTTP Upgrade response + Channels.setAttribute(channel, future); + + final NettyWebSocket webSocket = new NettyWebSocket(channel, responseHeaders); + handler.setWebSocket(webSocket); + channelManager.upgradePipelineForWebSockets(channel.pipeline()); + + // We don't need to synchronize as replacing the "ws-decoder" will + // process using the same thread. + try { + handler.onOpen(webSocket); + } catch (Exception ex) { + logger.warn("onSuccess unexpected exception", ex); + } + future.done(); + } + + private void abort(Channel channel, NettyResponseFuture future, WebSocketUpgradeHandler handler, HttpResponseStatus status) { + try { + handler.onThrowable(new IOException("Invalid Status code=" + status.getStatusCode() + " text=" + status.getStatusText())); + } finally { + finishUpdate(future, channel, true); + } + } + + @Override + public void handleRead(Channel channel, NettyResponseFuture future, Object e) throws Exception { + + if (e instanceof HttpResponse) { + HttpResponse response = (HttpResponse) e; + if (logger.isDebugEnabled()) { + HttpRequest httpRequest = future.getNettyRequest().getHttpRequest(); + logger.debug("\n\nRequest {}\n\nResponse {}\n", httpRequest, response); + } + + WebSocketUpgradeHandler handler = getWebSocketUpgradeHandler(future); + HttpResponseStatus status = new NettyResponseStatus(future.getUri(), response, channel); + HttpHeaders responseHeaders = response.headers(); + + if (!interceptors.exitAfterIntercept(channel, future, handler, response, status, responseHeaders)) { + if (handler.onStatusReceived(status) == State.CONTINUE) { + upgrade(channel, future, handler, response, responseHeaders); + } else { + abort(channel, future, handler, status); + } + } + + } else if (e instanceof WebSocketFrame) { + WebSocketFrame frame = (WebSocketFrame) e; + NettyWebSocket webSocket = getNettyWebSocket(future); + // retain because we might buffer the frame + if (webSocket.isReady()) { + 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 try to send immediately), we have to buffer + webSocket.bufferFrame(frame); + } + + } else if (!(e instanceof LastHttpContent)) { + // ignore, end of handshake response + logger.error("Invalid message {}", e); + } + } + + @Override + public void handleException(NettyResponseFuture future, Throwable e) { + logger.warn("onError", e); + + try { + NettyWebSocket webSocket = getNettyWebSocket(future); + if (webSocket != null) { + webSocket.onError(e); + webSocket.sendCloseFrame(); + } + } catch (Throwable t) { + logger.error("onError", t); + } + } + + @Override + public void handleChannelInactive(NettyResponseFuture future) { + logger.trace("Connection was closed abnormally (that is, with no close frame being received)."); + + try { + NettyWebSocket webSocket = getNettyWebSocket(future); + if (webSocket != null) { + webSocket.onClose(1006, "Connection was closed abnormally (that is, with no close frame being received)."); + } + } catch (Throwable t) { + logger.error("onError", t); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ConnectSuccessInterceptor.java b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ConnectSuccessInterceptor.java new file mode 100644 index 0000000000..22e29dbfb1 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ConnectSuccessInterceptor.java @@ -0,0 +1,61 @@ +/* + * 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.handler.intercept; + +import io.netty.channel.Channel; +import io.netty.util.concurrent.Future; +import org.asynchttpclient.Request; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.channel.ChannelManager; +import org.asynchttpclient.netty.request.NettyRequestSender; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.uri.Uri; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ConnectSuccessInterceptor { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConnectSuccessInterceptor.class); + + private final ChannelManager channelManager; + private final NettyRequestSender requestSender; + + ConnectSuccessInterceptor(ChannelManager channelManager, NettyRequestSender requestSender) { + this.channelManager = channelManager; + this.requestSender = requestSender; + } + + public boolean exitAfterHandlingConnect(Channel channel, NettyResponseFuture future, Request request, ProxyServer proxyServer) { + if (future.isKeepAlive()) { + future.attachChannel(channel, true); + } + + Uri requestUri = request.getUri(); + LOGGER.debug("Connecting to proxy {} for scheme {}", proxyServer, requestUri.getScheme()); + final Future whenHandshaked = channelManager.updatePipelineForHttpTunneling(channel.pipeline(), requestUri); + future.setReuseChannel(true); + future.setConnectAllowed(false); + + Request targetRequest = future.getTargetRequest().toBuilder().build(); + if (whenHandshaked == null) { + requestSender.drainChannelAndExecuteNextRequest(channel, future, targetRequest); + } else { + requestSender.drainChannelAndExecuteNextRequest(channel, future, targetRequest, whenHandshaked); + } + + return true; + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Continue100Interceptor.java b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Continue100Interceptor.java new file mode 100644 index 0000000000..aadd7f980a --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Continue100Interceptor.java @@ -0,0 +1,45 @@ +/* + * 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.handler.intercept; + +import io.netty.channel.Channel; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.OnLastHttpContentCallback; +import org.asynchttpclient.netty.channel.Channels; +import org.asynchttpclient.netty.request.NettyRequestSender; + +class Continue100Interceptor { + + private final NettyRequestSender requestSender; + + Continue100Interceptor(NettyRequestSender requestSender) { + this.requestSender = requestSender; + } + + public boolean exitAfterHandling100(final Channel channel, final NettyResponseFuture future) { + future.setHeadersAlreadyWrittenOnContinue(true); + future.setDontWriteBodyBecauseExpectContinue(false); + // directly send the body + Channels.setAttribute(channel, new OnLastHttpContentCallback(future) { + @Override + public void call() { + Channels.setAttribute(channel, future); + requestSender.writeRequest(future, channel); + } + }); + return true; + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Interceptors.java b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Interceptors.java new file mode 100644 index 0000000000..3de5bd40bb --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Interceptors.java @@ -0,0 +1,114 @@ +/* + * 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.handler.intercept; + +import io.netty.channel.Channel; +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.cookie.ClientCookieDecoder; +import io.netty.handler.codec.http.cookie.Cookie; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.Realm; +import org.asynchttpclient.Request; +import org.asynchttpclient.cookie.CookieStore; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.channel.ChannelManager; +import org.asynchttpclient.netty.request.NettyRequestSender; +import org.asynchttpclient.proxy.ProxyServer; + +import static io.netty.handler.codec.http.HttpHeaderNames.SET_COOKIE; +import static org.asynchttpclient.util.HttpConstants.ResponseStatusCodes.CONTINUE_100; +import static org.asynchttpclient.util.HttpConstants.ResponseStatusCodes.OK_200; +import static org.asynchttpclient.util.HttpConstants.ResponseStatusCodes.PROXY_AUTHENTICATION_REQUIRED_407; +import static org.asynchttpclient.util.HttpConstants.ResponseStatusCodes.UNAUTHORIZED_401; + +public class Interceptors { + + private final AsyncHttpClientConfig config; + private final Unauthorized401Interceptor unauthorized401Interceptor; + private final ProxyUnauthorized407Interceptor proxyUnauthorized407Interceptor; + private final Continue100Interceptor continue100Interceptor; + private final Redirect30xInterceptor redirect30xInterceptor; + private final ConnectSuccessInterceptor connectSuccessInterceptor; + private final ResponseFiltersInterceptor responseFiltersInterceptor; + private final boolean hasResponseFilters; + private final ClientCookieDecoder cookieDecoder; + + public Interceptors(AsyncHttpClientConfig config, + ChannelManager channelManager, + NettyRequestSender requestSender) { + this.config = config; + unauthorized401Interceptor = new Unauthorized401Interceptor(channelManager, requestSender); + proxyUnauthorized407Interceptor = new ProxyUnauthorized407Interceptor(channelManager, requestSender); + continue100Interceptor = new Continue100Interceptor(requestSender); + redirect30xInterceptor = new Redirect30xInterceptor(channelManager, config, requestSender); + connectSuccessInterceptor = new ConnectSuccessInterceptor(channelManager, requestSender); + responseFiltersInterceptor = new ResponseFiltersInterceptor(config, requestSender); + hasResponseFilters = !config.getResponseFilters().isEmpty(); + cookieDecoder = config.isUseLaxCookieEncoder() ? ClientCookieDecoder.LAX : ClientCookieDecoder.STRICT; + } + + public boolean exitAfterIntercept(Channel channel, NettyResponseFuture future, AsyncHandler handler, HttpResponse response, + HttpResponseStatus status, HttpHeaders responseHeaders) throws Exception { + + HttpRequest httpRequest = future.getNettyRequest().getHttpRequest(); + ProxyServer proxyServer = future.getProxyServer(); + int statusCode = response.status().code(); + Request request = future.getCurrentRequest(); + Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); + + // This MUST BE called before Redirect30xInterceptor because latter assumes cookie store is already updated + CookieStore cookieStore = config.getCookieStore(); + if (cookieStore != null) { + for (String cookieStr : responseHeaders.getAll(SET_COOKIE)) { + Cookie c = cookieDecoder.decode(cookieStr); + if (c != null) { + // Set-Cookie header could be invalid/malformed + cookieStore.add(future.getCurrentRequest().getUri(), c); + } + } + } + + if (hasResponseFilters && responseFiltersInterceptor.exitAfterProcessingFilters(channel, future, handler, status, responseHeaders)) { + return true; + } + + if (statusCode == UNAUTHORIZED_401) { + return unauthorized401Interceptor.exitAfterHandling401(channel, future, response, request, realm, httpRequest); + } + + if (statusCode == PROXY_AUTHENTICATION_REQUIRED_407) { + return proxyUnauthorized407Interceptor.exitAfterHandling407(channel, future, response, request, proxyServer, httpRequest); + } + + if (statusCode == CONTINUE_100) { + return continue100Interceptor.exitAfterHandling100(channel, future); + } + + if (Redirect30xInterceptor.REDIRECT_STATUSES.contains(statusCode)) { + return redirect30xInterceptor.exitAfterHandlingRedirect(channel, future, response, request, statusCode, realm); + } + + if (httpRequest.method() == HttpMethod.CONNECT && statusCode == OK_200) { + return connectSuccessInterceptor.exitAfterHandlingConnect(channel, future, request, proxyServer); + } + return false; + } +} 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 new file mode 100644 index 0000000000..b30f6bbd94 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ProxyUnauthorized407Interceptor.java @@ -0,0 +1,215 @@ +/* + * 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.handler.intercept; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpUtil; +import org.asynchttpclient.Realm; +import org.asynchttpclient.Realm.AuthScheme; +import org.asynchttpclient.Request; +import org.asynchttpclient.RequestBuilder; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.channel.ChannelManager; +import org.asynchttpclient.netty.channel.ChannelState; +import org.asynchttpclient.netty.request.NettyRequestSender; +import org.asynchttpclient.ntlm.NtlmEngine; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.spnego.SpnegoEngine; +import org.asynchttpclient.spnego.SpnegoEngineException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static io.netty.handler.codec.http.HttpHeaderNames.PROXY_AUTHENTICATE; +import static io.netty.handler.codec.http.HttpHeaderNames.PROXY_AUTHORIZATION; +import static org.asynchttpclient.Dsl.realm; +import static org.asynchttpclient.util.AuthenticatorUtils.NEGOTIATE; +import static org.asynchttpclient.util.AuthenticatorUtils.getHeaderWithPrefix; +import static org.asynchttpclient.util.HttpConstants.Methods.CONNECT; + +public class ProxyUnauthorized407Interceptor { + + private static final Logger LOGGER = LoggerFactory.getLogger(ProxyUnauthorized407Interceptor.class); + + private final ChannelManager channelManager; + private final NettyRequestSender requestSender; + + ProxyUnauthorized407Interceptor(ChannelManager channelManager, NettyRequestSender requestSender) { + this.channelManager = channelManager; + this.requestSender = requestSender; + } + + public boolean exitAfterHandling407(Channel channel, NettyResponseFuture future, HttpResponse response, Request request, + ProxyServer proxyServer, HttpRequest httpRequest) { + + if (future.isAndSetInProxyAuth(true)) { + LOGGER.info("Can't handle 407 as auth was already performed"); + return false; + } + + Realm proxyRealm = future.getProxyRealm(); + + if (proxyRealm == null) { + LOGGER.debug("Can't handle 407 as there's no proxyRealm"); + return false; + } + + List proxyAuthHeaders = response.headers().getAll(PROXY_AUTHENTICATE); + + if (proxyAuthHeaders.isEmpty()) { + LOGGER.info("Can't handle 407 as response doesn't contain Proxy-Authenticate headers"); + return false; + } + + // FIXME what's this??? + future.setChannelState(ChannelState.NEW); + HttpHeaders requestHeaders = new DefaultHttpHeaders().add(request.getHeaders()); + + switch (proxyRealm.getScheme()) { + case BASIC: + if (getHeaderWithPrefix(proxyAuthHeaders, "Basic") == null) { + LOGGER.info("Can't handle 407 with Basic realm as Proxy-Authenticate headers don't match"); + return false; + } + + if (proxyRealm.isUsePreemptiveAuth()) { + // FIXME do we need this, as future.getAndSetAuth + // was tested above? + // auth was already performed, most likely auth + // failed + LOGGER.info("Can't handle 407 with Basic realm as auth was preemptive and already performed"); + return false; + } + + // FIXME do we want to update the realm, or directly + // set the header? + Realm newBasicRealm = realm(proxyRealm) + .setUsePreemptiveAuth(true) + .build(); + future.setProxyRealm(newBasicRealm); + break; + + case DIGEST: + String digestHeader = getHeaderWithPrefix(proxyAuthHeaders, "Digest"); + if (digestHeader == null) { + LOGGER.info("Can't handle 407 with Digest realm as Proxy-Authenticate headers don't match"); + return false; + } + Realm newDigestRealm = realm(proxyRealm) + .setUri(request.getUri()) + .setMethodName(request.getMethod()) + .setUsePreemptiveAuth(true) + .parseProxyAuthenticateHeader(digestHeader) + .build(); + future.setProxyRealm(newDigestRealm); + break; + + case NTLM: + String ntlmHeader = getHeaderWithPrefix(proxyAuthHeaders, "NTLM"); + if (ntlmHeader == null) { + LOGGER.info("Can't handle 407 with NTLM realm as Proxy-Authenticate headers don't match"); + return false; + } + ntlmProxyChallenge(ntlmHeader, requestHeaders, proxyRealm, future); + Realm newNtlmRealm = realm(proxyRealm) + .setUsePreemptiveAuth(true) + .build(); + future.setProxyRealm(newNtlmRealm); + break; + + case KERBEROS: + case SPNEGO: + if (getHeaderWithPrefix(proxyAuthHeaders, NEGOTIATE) == null) { + LOGGER.info("Can't handle 407 with Kerberos or Spnego realm as Proxy-Authenticate headers don't match"); + return false; + } + try { + kerberosProxyChallenge(proxyRealm, proxyServer, requestHeaders); + } catch (SpnegoEngineException e) { + String ntlmHeader2 = getHeaderWithPrefix(proxyAuthHeaders, "NTLM"); + if (ntlmHeader2 != null) { + LOGGER.warn("Kerberos/Spnego proxy auth failed, proceeding with NTLM"); + ntlmProxyChallenge(ntlmHeader2, requestHeaders, proxyRealm, future); + Realm newNtlmRealm2 = realm(proxyRealm) + .setScheme(AuthScheme.NTLM) + .setUsePreemptiveAuth(true) + .build(); + future.setProxyRealm(newNtlmRealm2); + } else { + requestSender.abort(channel, future, e); + return false; + } + } + break; + default: + throw new IllegalStateException("Invalid Authentication scheme " + proxyRealm.getScheme()); + } + + RequestBuilder nextRequestBuilder = future.getCurrentRequest().toBuilder().setHeaders(requestHeaders); + if (future.getCurrentRequest().getUri().isSecured()) { + nextRequestBuilder.setMethod(CONNECT); + } + final Request nextRequest = nextRequestBuilder.build(); + + LOGGER.debug("Sending proxy authentication to {}", request.getUri()); + if (future.isKeepAlive() + && !HttpUtil.isTransferEncodingChunked(httpRequest) + && !HttpUtil.isTransferEncodingChunked(response)) { + future.setConnectAllowed(true); + future.setReuseChannel(true); + requestSender.drainChannelAndExecuteNextRequest(channel, future, nextRequest); + } else { + channelManager.closeChannel(channel); + requestSender.sendNextRequest(nextRequest, future); + } + + return true; + } + + private static void kerberosProxyChallenge(Realm proxyRealm, ProxyServer proxyServer, HttpHeaders headers) throws SpnegoEngineException { + String challengeHeader = SpnegoEngine.instance(proxyRealm.getPrincipal(), + proxyRealm.getPassword(), + proxyRealm.getServicePrincipalName(), + proxyRealm.getRealmName(), + proxyRealm.isUseCanonicalHostname(), + proxyRealm.getCustomLoginConfig(), + proxyRealm.getLoginContextName()).generateToken(proxyServer.getHost()); + headers.set(PROXY_AUTHORIZATION, NEGOTIATE + ' ' + challengeHeader); + } + + private static void ntlmProxyChallenge(String authenticateHeader, HttpHeaders requestHeaders, Realm proxyRealm, NettyResponseFuture future) { + if ("NTLM".equals(authenticateHeader)) { + // 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) + requestHeaders.set(PROXY_AUTHORIZATION, "NTLM " + challengeHeader); + future.setInProxyAuth(false); + } else { + String serverChallenge = authenticateHeader.substring("NTLM ".length()).trim(); + 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) + requestHeaders.set(PROXY_AUTHORIZATION, "NTLM " + challengeHeader); + } + } +} 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 new file mode 100644 index 0000000000..40628a7e51 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java @@ -0,0 +1,199 @@ +/* + * 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.handler.intercept; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpUtil; +import io.netty.handler.codec.http.cookie.Cookie; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.Realm; +import org.asynchttpclient.Realm.AuthScheme; +import org.asynchttpclient.Request; +import org.asynchttpclient.RequestBuilder; +import org.asynchttpclient.cookie.CookieStore; +import org.asynchttpclient.handler.MaxRedirectException; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.channel.ChannelManager; +import org.asynchttpclient.netty.request.NettyRequestSender; +import org.asynchttpclient.uri.Uri; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Set; + +import static io.netty.handler.codec.http.HttpHeaderNames.AUTHORIZATION; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.netty.handler.codec.http.HttpHeaderNames.HOST; +import static io.netty.handler.codec.http.HttpHeaderNames.LOCATION; +import static io.netty.handler.codec.http.HttpHeaderNames.PROXY_AUTHORIZATION; +import static org.asynchttpclient.util.HttpConstants.Methods.GET; +import static org.asynchttpclient.util.HttpConstants.Methods.HEAD; +import static org.asynchttpclient.util.HttpConstants.Methods.OPTIONS; +import static org.asynchttpclient.util.HttpConstants.ResponseStatusCodes.FOUND_302; +import static org.asynchttpclient.util.HttpConstants.ResponseStatusCodes.MOVED_PERMANENTLY_301; +import static org.asynchttpclient.util.HttpConstants.ResponseStatusCodes.PERMANENT_REDIRECT_308; +import static org.asynchttpclient.util.HttpConstants.ResponseStatusCodes.SEE_OTHER_303; +import static org.asynchttpclient.util.HttpConstants.ResponseStatusCodes.TEMPORARY_REDIRECT_307; +import static org.asynchttpclient.util.HttpUtils.followRedirect; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; +import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace; + +public class Redirect30xInterceptor { + + public static final Set REDIRECT_STATUSES = new HashSet<>(); + private static final Logger LOGGER = LoggerFactory.getLogger(Redirect30xInterceptor.class); + + static { + REDIRECT_STATUSES.add(MOVED_PERMANENTLY_301); + REDIRECT_STATUSES.add(FOUND_302); + REDIRECT_STATUSES.add(SEE_OTHER_303); + REDIRECT_STATUSES.add(TEMPORARY_REDIRECT_307); + REDIRECT_STATUSES.add(PERMANENT_REDIRECT_308); + } + + private final ChannelManager channelManager; + 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"); + } + + public boolean exitAfterHandlingRedirect(Channel channel, NettyResponseFuture future, HttpResponse response, Request request, + int statusCode, Realm realm) throws Exception { + + if (followRedirect(config, request)) { + if (future.incrementAndGetCurrentRedirectCount() >= config.getMaxRedirects()) { + throw maxRedirectException; + + } else { + // We must allow auth handling again. + future.setInAuth(false); + future.setInProxyAuth(false); + + String originalMethod = request.getMethod(); + boolean switchToGet = !originalMethod.equals(GET) && + !originalMethod.equals(OPTIONS) && + !originalMethod.equals(HEAD) && + (statusCode == MOVED_PERMANENTLY_301 || statusCode == SEE_OTHER_303 || statusCode == FOUND_302 && !config.isStrict302Handling()); + boolean keepBody = statusCode == TEMPORARY_REDIRECT_307 || statusCode == PERMANENT_REDIRECT_308 || statusCode == FOUND_302 && config.isStrict302Handling(); + + final RequestBuilder requestBuilder = new RequestBuilder(switchToGet ? GET : originalMethod) + .setChannelPoolPartitioning(request.getChannelPoolPartitioning()) + .setFollowRedirect(true) + .setLocalAddress(request.getLocalAddress()) + .setNameResolver(request.getNameResolver()) + .setProxyServer(request.getProxyServer()) + .setRealm(request.getRealm()) + .setRequestTimeout(request.getRequestTimeout()); + + if (keepBody) { + requestBuilder.setCharset(request.getCharset()); + if (isNonEmpty(request.getFormParams())) { + requestBuilder.setFormParams(request.getFormParams()); + } else if (request.getStringData() != null) { + requestBuilder.setBody(request.getStringData()); + } else if (request.getByteData() != null) { + requestBuilder.setBody(request.getByteData()); + } else if (request.getByteBufferData() != null) { + requestBuilder.setBody(request.getByteBufferData()); + } else if (request.getBodyGenerator() != null) { + requestBuilder.setBody(request.getBodyGenerator()); + } else if (isNonEmpty(request.getBodyParts())) { + requestBuilder.setBodyParts(request.getBodyParts()); + } + } + + requestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody, stripAuthorizationOnRedirect)); + + // in case of a redirect from HTTP to HTTPS, future + // attributes might change + final boolean initialConnectionKeepAlive = future.isKeepAlive(); + final Object initialPartitionKey = future.getPartitionKey(); + + HttpHeaders responseHeaders = response.headers(); + String location = responseHeaders.get(LOCATION); + Uri newUri = Uri.create(future.getUri(), location); + LOGGER.debug("Redirecting to {}", newUri); + + CookieStore cookieStore = config.getCookieStore(); + if (cookieStore != null) { + // Update request's cookies assuming that cookie store is already updated by Interceptors + for (Cookie cookie : cookieStore.get(newUri)) { + requestBuilder.addCookieIfUnset(cookie); + } + } + + boolean sameBase = request.getUri().isSameBase(newUri); + if (sameBase) { + // we can only assume the virtual host is still valid if the baseUrl is the same + requestBuilder.setVirtualHost(request.getVirtualHost()); + } + + final Request nextRequest = requestBuilder.setUri(newUri).build(); + future.setTargetRequest(nextRequest); + + LOGGER.debug("Sending redirect to {}", newUri); + + if (future.isKeepAlive() && !HttpUtil.isTransferEncodingChunked(response)) { + if (sameBase) { + future.setReuseChannel(true); + // we can't directly send the next request because we still have to received LastContent + requestSender.drainChannelAndExecuteNextRequest(channel, future, nextRequest); + } else { + channelManager.drainChannelAndOffer(channel, future, initialConnectionKeepAlive, initialPartitionKey); + requestSender.sendNextRequest(nextRequest, future); + } + + } else { + // redirect + chunking = WAT + channelManager.closeChannel(channel); + requestSender.sendNextRequest(nextRequest, future); + } + + return true; + } + } + return false; + } + + private static HttpHeaders propagatedHeaders(Request request, Realm realm, boolean keepBody, boolean stripAuthorization) { + HttpHeaders headers = request.getHeaders() + .remove(HOST) + .remove(CONTENT_LENGTH); + + if (!keepBody) { + headers.remove(CONTENT_TYPE); + } + + if (stripAuthorization || (realm != null && realm.getScheme() == AuthScheme.NTLM)) { + headers.remove(AUTHORIZATION) + .remove(PROXY_AUTHORIZATION); + } + return headers; + } +} 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 new file mode 100644 index 0000000000..5f905d94f3 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/ResponseFiltersInterceptor.java @@ -0,0 +1,69 @@ +/* + * 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.handler.intercept; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.HttpHeaders; +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.ResponseFilter; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.request.NettyRequestSender; + +public class ResponseFiltersInterceptor { + + private final AsyncHttpClientConfig config; + private final NettyRequestSender requestSender; + + ResponseFiltersInterceptor(AsyncHttpClientConfig config, NettyRequestSender requestSender) { + this.config = config; + this.requestSender = requestSender; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public boolean exitAfterProcessingFilters(Channel channel, + NettyResponseFuture future, + AsyncHandler handler, + HttpResponseStatus status, + HttpHeaders responseHeaders) { + + FilterContext fc = new FilterContext.FilterContextBuilder(handler, future.getCurrentRequest()).responseStatus(status) + .responseHeaders(responseHeaders).build(); + + for (ResponseFilter asyncFilter : config.getResponseFilters()) { + try { + fc = asyncFilter.filter(fc); + // FIXME Is it worth protecting against this? +// requireNonNull(fc, "filterContext"); + } catch (FilterException fe) { + requestSender.abort(channel, future, fe); + } + } + + // The handler may have been wrapped. + future.setAsyncHandler(fc.getAsyncHandler()); + + // The request has changed + if (fc.replayRequest()) { + requestSender.replayRequest(future, fc, channel); + return true; + } + return false; + } +} 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 new file mode 100644 index 0000000000..cb89f70b83 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/handler/intercept/Unauthorized401Interceptor.java @@ -0,0 +1,211 @@ +/* + * 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.handler.intercept; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpUtil; +import org.asynchttpclient.Realm; +import org.asynchttpclient.Realm.AuthScheme; +import org.asynchttpclient.Request; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.channel.ChannelManager; +import org.asynchttpclient.netty.channel.ChannelState; +import org.asynchttpclient.netty.request.NettyRequestSender; +import org.asynchttpclient.ntlm.NtlmEngine; +import org.asynchttpclient.spnego.SpnegoEngine; +import org.asynchttpclient.spnego.SpnegoEngineException; +import org.asynchttpclient.uri.Uri; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static io.netty.handler.codec.http.HttpHeaderNames.AUTHORIZATION; +import static io.netty.handler.codec.http.HttpHeaderNames.WWW_AUTHENTICATE; +import static org.asynchttpclient.Dsl.realm; +import static org.asynchttpclient.util.AuthenticatorUtils.NEGOTIATE; +import static org.asynchttpclient.util.AuthenticatorUtils.getHeaderWithPrefix; +import static org.asynchttpclient.util.MiscUtils.withDefault; + +public class Unauthorized401Interceptor { + + private static final Logger LOGGER = LoggerFactory.getLogger(Unauthorized401Interceptor.class); + + private final ChannelManager channelManager; + private final NettyRequestSender requestSender; + + Unauthorized401Interceptor(ChannelManager channelManager, NettyRequestSender requestSender) { + this.channelManager = channelManager; + this.requestSender = requestSender; + } + + public boolean exitAfterHandling401(Channel channel, NettyResponseFuture future, HttpResponse response, Request request, Realm realm, HttpRequest httpRequest) { + if (realm == null) { + LOGGER.debug("Can't handle 401 as there's no realm"); + return false; + } + + if (future.isAndSetInAuth(true)) { + LOGGER.info("Can't handle 401 as auth was already performed"); + return false; + } + + List wwwAuthHeaders = response.headers().getAll(WWW_AUTHENTICATE); + + if (wwwAuthHeaders.isEmpty()) { + LOGGER.info("Can't handle 401 as response doesn't contain WWW-Authenticate headers"); + return false; + } + + // FIXME what's this??? + future.setChannelState(ChannelState.NEW); + HttpHeaders requestHeaders = new DefaultHttpHeaders().add(request.getHeaders()); + + switch (realm.getScheme()) { + case BASIC: + if (getHeaderWithPrefix(wwwAuthHeaders, "Basic") == null) { + LOGGER.info("Can't handle 401 with Basic realm as WWW-Authenticate headers don't match"); + return false; + } + + if (realm.isUsePreemptiveAuth()) { + // FIXME do we need this, as future.getAndSetAuth + // was tested above? + // auth was already performed, most likely auth + // failed + LOGGER.info("Can't handle 401 with Basic realm as auth was preemptive and already performed"); + return false; + } + + // FIXME do we want to update the realm, or directly + // set the header? + Realm newBasicRealm = realm(realm) + .setUsePreemptiveAuth(true) + .build(); + future.setRealm(newBasicRealm); + break; + + case DIGEST: + String digestHeader = getHeaderWithPrefix(wwwAuthHeaders, "Digest"); + if (digestHeader == null) { + LOGGER.info("Can't handle 401 with Digest realm as WWW-Authenticate headers don't match"); + return false; + } + Realm newDigestRealm = realm(realm) + .setUri(request.getUri()) + .setMethodName(request.getMethod()) + .setUsePreemptiveAuth(true) + .parseWWWAuthenticateHeader(digestHeader) + .build(); + future.setRealm(newDigestRealm); + break; + + case NTLM: + String ntlmHeader = getHeaderWithPrefix(wwwAuthHeaders, "NTLM"); + if (ntlmHeader == null) { + LOGGER.info("Can't handle 401 with NTLM realm as WWW-Authenticate headers don't match"); + return false; + } + + ntlmChallenge(ntlmHeader, requestHeaders, realm, future); + Realm newNtlmRealm = realm(realm) + .setUsePreemptiveAuth(true) + .build(); + future.setRealm(newNtlmRealm); + break; + + case KERBEROS: + case SPNEGO: + if (getHeaderWithPrefix(wwwAuthHeaders, NEGOTIATE) == null) { + LOGGER.info("Can't handle 401 with Kerberos or Spnego realm as WWW-Authenticate headers don't match"); + return false; + } + try { + kerberosChallenge(realm, request, requestHeaders); + } catch (SpnegoEngineException e) { + String ntlmHeader2 = getHeaderWithPrefix(wwwAuthHeaders, "NTLM"); + if (ntlmHeader2 != null) { + LOGGER.warn("Kerberos/Spnego auth failed, proceeding with NTLM"); + ntlmChallenge(ntlmHeader2, requestHeaders, realm, future); + Realm newNtlmRealm2 = realm(realm) + .setScheme(AuthScheme.NTLM) + .setUsePreemptiveAuth(true) + .build(); + future.setRealm(newNtlmRealm2); + } else { + requestSender.abort(channel, future, e); + return false; + } + } + break; + default: + throw new IllegalStateException("Invalid Authentication scheme " + realm.getScheme()); + } + + final Request nextRequest = future.getCurrentRequest().toBuilder().setHeaders(requestHeaders).build(); + + LOGGER.debug("Sending authentication to {}", request.getUri()); + if (future.isKeepAlive() && !HttpUtil.isTransferEncodingChunked(httpRequest) && !HttpUtil.isTransferEncodingChunked(response)) { + future.setReuseChannel(true); + requestSender.drainChannelAndExecuteNextRequest(channel, future, nextRequest); + } else { + channelManager.closeChannel(channel); + requestSender.sendNextRequest(nextRequest, future); + } + + return true; + } + + private static void ntlmChallenge(String authenticateHeader, + HttpHeaders requestHeaders, + Realm realm, + NettyResponseFuture future) { + + if ("NTLM".equals(authenticateHeader)) { + // 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) + requestHeaders.set(AUTHORIZATION, "NTLM " + challengeHeader); + future.setInAuth(false); + + } else { + String serverChallenge = authenticateHeader.substring("NTLM ".length()).trim(); + 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) + requestHeaders.set(AUTHORIZATION, "NTLM " + challengeHeader); + } + } + + private static void kerberosChallenge(Realm realm, Request request, HttpHeaders headers) throws SpnegoEngineException { + Uri uri = request.getUri(); + String host = withDefault(request.getVirtualHost(), uri.getHost()); + String challengeHeader = SpnegoEngine.instance(realm.getPrincipal(), + realm.getPassword(), + realm.getServicePrincipalName(), + realm.getRealmName(), + realm.isUseCanonicalHostname(), + realm.getCustomLoginConfig(), + realm.getLoginContextName()).generateToken(host); + headers.set(AUTHORIZATION, NEGOTIATE + ' ' + challengeHeader); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java new file mode 100755 index 0000000000..71cc658a0a --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java @@ -0,0 +1,38 @@ +/* + * 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. + * 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; + +import io.netty.handler.codec.http.HttpRequest; +import org.asynchttpclient.netty.request.body.NettyBody; + +public final class NettyRequest { + + private final HttpRequest httpRequest; + private final NettyBody body; + + NettyRequest(HttpRequest httpRequest, NettyBody body) { + this.httpRequest = httpRequest; + this.body = body; + } + + public HttpRequest getHttpRequest() { + return httpRequest; + } + + public NettyBody getBody() { + return body; + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java new file mode 100755 index 0000000000..67d9a67be6 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java @@ -0,0 +1,286 @@ +/* + * 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. + * 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; + +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; +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.HttpVersion; +import io.netty.handler.codec.http.cookie.ClientCookieEncoder; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.Realm; +import org.asynchttpclient.Request; +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; +import org.asynchttpclient.netty.request.body.NettyFileBody; +import org.asynchttpclient.netty.request.body.NettyInputStreamBody; +import org.asynchttpclient.netty.request.body.NettyMultipartBody; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.request.body.generator.FileBodyGenerator; +import org.asynchttpclient.request.body.generator.InputStreamBodyGenerator; +import org.asynchttpclient.uri.Uri; +import org.asynchttpclient.util.StringUtils; + +import java.nio.charset.Charset; + +import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT; +import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT_ENCODING; +import static io.netty.handler.codec.http.HttpHeaderNames.AUTHORIZATION; +import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.netty.handler.codec.http.HttpHeaderNames.COOKIE; +import static io.netty.handler.codec.http.HttpHeaderNames.HOST; +import static io.netty.handler.codec.http.HttpHeaderNames.ORIGIN; +import static io.netty.handler.codec.http.HttpHeaderNames.PROXY_AUTHORIZATION; +import static io.netty.handler.codec.http.HttpHeaderNames.SEC_WEBSOCKET_KEY; +import static io.netty.handler.codec.http.HttpHeaderNames.SEC_WEBSOCKET_VERSION; +import static io.netty.handler.codec.http.HttpHeaderNames.TRANSFER_ENCODING; +import static io.netty.handler.codec.http.HttpHeaderNames.UPGRADE; +import static io.netty.handler.codec.http.HttpHeaderNames.USER_AGENT; +import static org.asynchttpclient.util.AuthenticatorUtils.perRequestAuthorizationHeader; +import static org.asynchttpclient.util.AuthenticatorUtils.perRequestProxyAuthorizationHeader; +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; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; +import static org.asynchttpclient.ws.WebSocketUtils.getWebSocketKey; + +public final class NettyRequestFactory { + + private static final Integer ZERO_CONTENT_LENGTH = 0; + + private final AsyncHttpClientConfig config; + private final ClientCookieEncoder cookieEncoder; + + NettyRequestFactory(AsyncHttpClientConfig config) { + this.config = config; + cookieEncoder = config.isUseLaxCookieEncoder() ? ClientCookieEncoder.LAX : ClientCookieEncoder.STRICT; + } + + private NettyBody body(Request request) { + NettyBody nettyBody = null; + Charset bodyCharset = request.getCharset(); + + if (request.getByteData() != null) { + nettyBody = new NettyByteArrayBody(request.getByteData()); + } else if (request.getCompositeByteData() != null) { + nettyBody = new NettyCompositeByteArrayBody(request.getCompositeByteData()); + } else if (request.getStringData() != null) { + 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())) { + CharSequence contentTypeOverride = request.getHeaders().contains(CONTENT_TYPE) ? null : HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED; + nettyBody = new NettyByteBufferBody(urlEncodeFormParams(request.getFormParams(), bodyCharset), contentTypeOverride); + } else if (isNonEmpty(request.getBodyParts())) { + nettyBody = new NettyMultipartBody(request.getBodyParts(), request.getHeaders(), config); + } else if (request.getFile() != null) { + nettyBody = new NettyFileBody(request.getFile(), config); + } else if (request.getBodyGenerator() instanceof FileBodyGenerator) { + FileBodyGenerator fileBodyGenerator = (FileBodyGenerator) request.getBodyGenerator(); + nettyBody = new NettyFileBody(fileBodyGenerator.getFile(), fileBodyGenerator.getRegionSeek(), fileBodyGenerator.getRegionLength(), config); + } else if (request.getBodyGenerator() instanceof InputStreamBodyGenerator) { + InputStreamBodyGenerator inStreamGenerator = (InputStreamBodyGenerator) request.getBodyGenerator(); + nettyBody = new NettyInputStreamBody(inStreamGenerator.getInputStream(), inStreamGenerator.getContentLength()); + } else if (request.getBodyGenerator() != null) { + nettyBody = new NettyBodyBody(request.getBodyGenerator().createBody(), config); + } + + return nettyBody; + } + + public void addAuthorizationHeader(HttpHeaders headers, String authorizationHeader) { + if (authorizationHeader != null) { + // don't override authorization but append + headers.add(AUTHORIZATION, authorizationHeader); + } + } + + public void setProxyAuthorizationHeader(HttpHeaders headers, String proxyAuthorizationHeader) { + if (proxyAuthorizationHeader != null) { + headers.set(PROXY_AUTHORIZATION, proxyAuthorizationHeader); + } + } + + public NettyRequest newNettyRequest(Request request, boolean performConnectRequest, ProxyServer proxyServer, Realm realm, Realm proxyRealm) { + Uri uri = request.getUri(); + HttpMethod method = performConnectRequest ? HttpMethod.CONNECT : HttpMethod.valueOf(request.getMethod()); + boolean connect = method == HttpMethod.CONNECT; + + HttpVersion httpVersion = HttpVersion.HTTP_1_1; + String requestUri = requestUri(uri, proxyServer, connect); + + NettyBody body = connect ? null : body(request); + + NettyRequest nettyRequest; + if (body == null) { + HttpRequest httpRequest = new DefaultFullHttpRequest(httpVersion, method, requestUri, Unpooled.EMPTY_BUFFER); + nettyRequest = new NettyRequest(httpRequest, null); + + } else if (body instanceof NettyDirectBody) { + ByteBuf buf = ((NettyDirectBody) body).byteBuf(); + HttpRequest httpRequest = new DefaultFullHttpRequest(httpVersion, method, requestUri, buf); + // body is passed as null as it's written directly with the request + nettyRequest = new NettyRequest(httpRequest, null); + } else { + HttpRequest httpRequest = new DefaultHttpRequest(httpVersion, method, requestUri); + nettyRequest = new NettyRequest(httpRequest, body); + } + + HttpHeaders headers = nettyRequest.getHttpRequest().headers(); + + if (connect) { + // assign proxy-auth as configured on request + headers.set(PROXY_AUTHORIZATION, request.getHeaders().getAll(PROXY_AUTHORIZATION)); + headers.set(USER_AGENT, request.getHeaders().getAll(USER_AGENT)); + + } else { + // assign headers as configured on request + headers.set(request.getHeaders()); + + if (isNonEmpty(request.getCookies())) { + headers.set(COOKIE, cookieEncoder.encode(request.getCookies())); + } + + String userDefinedAcceptEncoding = headers.get(ACCEPT_ENCODING); + if (userDefinedAcceptEncoding != null) { + if (config.isEnableAutomaticDecompression()) { + 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); + } + } + } + + if (!headers.contains(CONTENT_LENGTH)) { + if (body != null) { + if (body.getContentLength() < 0) { + headers.set(TRANSFER_ENCODING, HttpHeaderValues.CHUNKED); + } else { + headers.set(CONTENT_LENGTH, body.getContentLength()); + } + } else if (method == HttpMethod.POST || method == HttpMethod.PUT || method == HttpMethod.PATCH) { + headers.set(CONTENT_LENGTH, ZERO_CONTENT_LENGTH); + } + } + + if (body != null && body.getContentTypeOverride() != null) { + headers.set(CONTENT_TYPE, body.getContentTypeOverride()); + } + + // connection header and friends + if (!connect && uri.isWebSocket()) { + headers.set(UPGRADE, HttpHeaderValues.WEBSOCKET) + .set(CONNECTION, HttpHeaderValues.UPGRADE) + .set(SEC_WEBSOCKET_KEY, getWebSocketKey()) + .set(SEC_WEBSOCKET_VERSION, "13"); + + if (!headers.contains(ORIGIN)) { + headers.set(ORIGIN, originHeader(uri)); + } + + } else if (!headers.contains(CONNECTION)) { + CharSequence connectionHeaderValue = connectionHeader(config.isKeepAlive(), httpVersion); + if (connectionHeaderValue != null) { + headers.set(CONNECTION, connectionHeaderValue); + } + } + + if (!headers.contains(HOST)) { + String virtualHost = request.getVirtualHost(); + headers.set(HOST, virtualHost != null ? virtualHost : hostHeader(uri)); + } + + // don't override authorization but append + addAuthorizationHeader(headers, perRequestAuthorizationHeader(request, realm)); + // only set proxy auth on request over plain HTTP, or when performing CONNECT + if (!uri.isSecured() || connect) { + setProxyAuthorizationHeader(headers, perRequestProxyAuthorizationHeader(request, proxyRealm)); + } + + // Add default accept headers + if (!headers.contains(ACCEPT)) { + headers.set(ACCEPT, ACCEPT_ALL_HEADER_VALUE); + } + + // Add default user agent + if (!headers.contains(USER_AGENT) && config.getUserAgent() != null) { + headers.set(USER_AGENT, config.getUserAgent()); + } + + return nettyRequest; + } + + private static String requestUri(Uri uri, ProxyServer proxyServer, boolean connect) { + if (connect) { + // proxy tunnelling, connect need host and explicit port + return uri.getAuthority(); + + } else if (proxyServer != null && !uri.isSecured() && proxyServer.getProxyType().isHttp()) { + // proxy over HTTP, need full url + return uri.toUrl(); + + } else { + // direct connection to target host or tunnel already connected: only path and query + return uri.toRelativeUrl(); + } + } + + private static CharSequence connectionHeader(boolean keepAlive, HttpVersion httpVersion) { + if (httpVersion.isKeepAliveDefault()) { + return keepAlive ? null : HttpHeaderValues.CLOSE; + } else { + return keepAlive ? HttpHeaderValues.KEEP_ALIVE : null; + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java new file mode 100755 index 0000000000..b66dd713df --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java @@ -0,0 +1,621 @@ +/* + * 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. + * 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; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelProgressivePromise; +import io.netty.channel.ChannelPromise; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.util.Timer; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.ImmediateEventExecutor; +import io.netty.util.concurrent.Promise; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.AsyncHttpClientState; +import org.asynchttpclient.ListenableFuture; +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.IOExceptionFilter; +import org.asynchttpclient.handler.TransferCompletionHandler; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.OnLastHttpContentCallback; +import org.asynchttpclient.netty.SimpleFutureListener; +import org.asynchttpclient.netty.channel.ChannelManager; +import org.asynchttpclient.netty.channel.ChannelState; +import org.asynchttpclient.netty.channel.Channels; +import org.asynchttpclient.netty.channel.ConnectionSemaphore; +import org.asynchttpclient.netty.channel.DefaultConnectionSemaphoreFactory; +import org.asynchttpclient.netty.channel.NettyChannelConnector; +import org.asynchttpclient.netty.channel.NettyConnectListener; +import org.asynchttpclient.netty.timeout.TimeoutsHolder; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.resolver.RequestHostnameResolver; +import org.asynchttpclient.uri.Uri; +import org.asynchttpclient.ws.WebSocketUpgradeHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.List; + +import static io.netty.handler.codec.http.HttpHeaderNames.EXPECT; +import static java.util.Collections.singletonList; +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; +import static org.asynchttpclient.util.HttpConstants.Methods.GET; +import static org.asynchttpclient.util.MiscUtils.getCause; +import static org.asynchttpclient.util.ProxyUtils.getProxyServer; + +public final class NettyRequestSender { + + private static final Logger LOGGER = LoggerFactory.getLogger(NettyRequestSender.class); + + private final AsyncHttpClientConfig config; + private final ChannelManager channelManager; + private final ConnectionSemaphore connectionSemaphore; + private final Timer nettyTimer; + private final AsyncHttpClientState clientState; + private final NettyRequestFactory requestFactory; + + public NettyRequestSender(AsyncHttpClientConfig config, ChannelManager channelManager, Timer nettyTimer, AsyncHttpClientState clientState) { + this.config = config; + this.channelManager = channelManager; + connectionSemaphore = config.getConnectionSemaphoreFactory() == null + ? new DefaultConnectionSemaphoreFactory().newConnectionSemaphore(config) + : config.getConnectionSemaphoreFactory().newConnectionSemaphore(config); + this.nettyTimer = nettyTimer; + this.clientState = clientState; + 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"); + } + + validateWebSocketRequest(request, asyncHandler); + ProxyServer proxyServer = getProxyServer(config, request); + + // WebSockets use connect tunneling to work with proxies + 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 on if we can pool or connection or if we have to open a new one + return sendRequestThroughProxy(request, asyncHandler, future, proxyServer); + } + } else { + // no CONNECT for sure + return sendRequestWithCertainForceConnect(request, asyncHandler, future, proxyServer, false); + } + } + + 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); + } + + /** + * We know for sure if we have to force to connect or not, so we can build the + * HttpRequest right away This reduces the probability of having a pooled + * channel closed by the server by the time we build the request + */ + private ListenableFuture sendRequestWithCertainForceConnect(Request request, AsyncHandler asyncHandler, NettyResponseFuture future, + ProxyServer proxyServer, boolean performConnectRequest) { + Channel channel = getOpenChannel(future, request, proxyServer, 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 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, + AsyncHandler asyncHandler, + NettyResponseFuture future, + ProxyServer proxyServer) { + + NettyResponseFuture newFuture = null; + for (int i = 0; i < 3; i++) { + Channel channel = getOpenChannel(future, request, proxyServer, asyncHandler); + if (channel == null) { + // pool is empty + break; + } + + if (newFuture == null) { + newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, proxyServer, false); + } + + if (Channels.isChannelActive(channel)) { + // if the channel is still active, we can use it, + // otherwise, channel was closed by the time we computed the request, try again + return sendRequestWithOpenChannel(newFuture, asyncHandler, channel); + } + } + + // couldn't poll an active channel + newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, proxyServer, true); + return sendRequestWithNewChannel(request, proxyServer, newFuture, asyncHandler); + } + + private NettyResponseFuture newNettyRequestAndResponseFuture(final Request request, final AsyncHandler asyncHandler, NettyResponseFuture originalFuture, + ProxyServer proxy, boolean performConnectRequest) { + Realm realm; + if (originalFuture != null) { + realm = originalFuture.getRealm(); + } else { + realm = request.getRealm(); + if (realm == null) { + realm = config.getRealm(); + } + } + + Realm proxyRealm = null; + if (originalFuture != null) { + proxyRealm = originalFuture.getProxyRealm(); + } else if (proxy != null) { + proxyRealm = proxy.getRealm(); + } + + NettyRequest nettyRequest = requestFactory.newNettyRequest(request, performConnectRequest, proxy, realm, proxyRealm); + if (originalFuture == null) { + NettyResponseFuture future = newNettyResponseFuture(request, asyncHandler, nettyRequest, proxy); + future.setRealm(realm); + future.setProxyRealm(proxyRealm); + return future; + } else { + originalFuture.setNettyRequest(nettyRequest); + originalFuture.setCurrentRequest(request); + return originalFuture; + } + } + + private Channel getOpenChannel(NettyResponseFuture future, Request request, ProxyServer proxyServer, AsyncHandler asyncHandler) { + if (future != null && future.isReuseChannel() && Channels.isChannelActive(future.channel())) { + return future.channel(); + } else { + return pollPooledChannel(request, proxyServer, asyncHandler); + } + } + + private ListenableFuture sendRequestWithOpenChannel(NettyResponseFuture future, AsyncHandler asyncHandler, Channel channel) { + try { + asyncHandler.onConnectionPooled(channel); + } catch (Exception e) { + LOGGER.error("onConnectionPooled crashed", e); + abort(channel, future, e); + return future; + } + + SocketAddress channelRemoteAddress = channel.remoteAddress(); + if (channelRemoteAddress != null) { + // otherwise, bad luck, the channel was closed, see bellow + scheduleRequestTimeout(future, (InetSocketAddress) channelRemoteAddress); + } + + future.setChannelState(ChannelState.POOLED); + future.attachChannel(channel, false); + + if (LOGGER.isDebugEnabled()) { + HttpRequest httpRequest = future.getNettyRequest().getHttpRequest(); + LOGGER.debug("Using open Channel {} for {} '{}'", channel, httpRequest.method(), httpRequest.uri()); + } + + // channelInactive might be called between isChannelValid and writeRequest + // so if we don't store the Future now, channelInactive won't perform + // handleUnexpectedClosedChannel + Channels.setAttribute(channel, future); + + if (Channels.isChannelActive(channel)) { + writeRequest(future, channel); + } else { + // bad luck, the channel was closed in-between + // there's a very good chance onClose was already notified but the + // future wasn't already registered + handleUnexpectedClosedChannel(channel, future); + } + + return future; + } + + private ListenableFuture sendRequestWithNewChannel(Request request, ProxyServer proxy, NettyResponseFuture future, AsyncHandler asyncHandler) { + // some headers are only set when performing the first request + HttpHeaders headers = future.getNettyRequest().getHttpRequest().headers(); + if (proxy != null && proxy.getCustomHeaders() != null) { + HttpHeaders customHeaders = proxy.getCustomHeaders().apply(request); + if (customHeaders != null) { + headers.add(customHeaders); + } + } + Realm realm = future.getRealm(); + Realm proxyRealm = future.getProxyRealm(); + requestFactory.addAuthorizationHeader(headers, perConnectionAuthorizationHeader(request, proxy, realm)); + requestFactory.setProxyAuthorizationHeader(headers, perConnectionProxyAuthorizationHeader(request, proxyRealm)); + + future.setInAuth(realm != null && realm.isUsePreemptiveAuth() && realm.getScheme() != AuthScheme.NTLM); + future.setInProxyAuth(proxyRealm != null && proxyRealm.isUsePreemptiveAuth() && proxyRealm.getScheme() != AuthScheme.NTLM); + + try { + if (!channelManager.isOpen()) { + throw PoolAlreadyClosedException.INSTANCE; + } + + // Do not throw an exception when we need an extra connection for a + // redirect. + future.acquirePartitionLockLazily(); + } catch (Throwable t) { + abort(null, future, getCause(t)); + // exit and don't try to resolve address + return future; + } + + resolveAddresses(request, proxy, future, asyncHandler).addListener(new SimpleFutureListener>() { + + @Override + protected void onSuccess(List addresses) { + NettyConnectListener connectListener = new NettyConnectListener<>(future, NettyRequestSender.this, channelManager, connectionSemaphore); + NettyChannelConnector connector = new NettyChannelConnector(request.getLocalAddress(), addresses, asyncHandler, clientState); + if (!future.isDone()) { + // Do not throw an exception when we need an extra connection for a redirect + // FIXME why? This violate the max connection per host handling, right? + channelManager.getBootstrap(request.getUri(), request.getNameResolver(), proxy).addListener((Future whenBootstrap) -> { + if (whenBootstrap.isSuccess()) { + connector.connect(whenBootstrap.get(), connectListener); + } else { + abort(null, future, whenBootstrap.cause()); + } + }); + } + } + + @Override + protected void onFailure(Throwable cause) { + abort(null, future, getCause(cause)); + } + }); + + return future; + } + + private Future> resolveAddresses(Request request, ProxyServer proxy, NettyResponseFuture future, AsyncHandler asyncHandler) { + Uri uri = request.getUri(); + final Promise> promise = ImmediateEventExecutor.INSTANCE.newPromise(); + + if (proxy != null && !proxy.isIgnoredForHost(uri.getHost()) && proxy.getProxyType().isHttp()) { + int port = uri.isSecured() ? proxy.getSecuredPort() : proxy.getPort(); + InetSocketAddress unresolvedRemoteAddress = InetSocketAddress.createUnresolved(proxy.getHost(), port); + scheduleRequestTimeout(future, unresolvedRemoteAddress); + return RequestHostnameResolver.INSTANCE.resolve(request.getNameResolver(), unresolvedRemoteAddress, asyncHandler); + } else { + int port = uri.getExplicitPort(); + + InetSocketAddress unresolvedRemoteAddress = InetSocketAddress.createUnresolved(uri.getHost(), port); + scheduleRequestTimeout(future, unresolvedRemoteAddress); + + if (request.getAddress() != null) { + // bypass resolution + InetSocketAddress inetSocketAddress = new InetSocketAddress(request.getAddress(), port); + return promise.setSuccess(singletonList(inetSocketAddress)); + } else { + return RequestHostnameResolver.INSTANCE.resolve(request.getNameResolver(), unresolvedRemoteAddress, asyncHandler); + } + } + } + + private NettyResponseFuture newNettyResponseFuture(Request request, AsyncHandler asyncHandler, NettyRequest nettyRequest, ProxyServer proxyServer) { + NettyResponseFuture future = new NettyResponseFuture<>( + request, + asyncHandler, + nettyRequest, + config.getMaxRequestRetry(), + request.getChannelPoolPartitioning(), + connectionSemaphore, + proxyServer); + + String expectHeader = request.getHeaders().get(EXPECT); + if (HttpHeaderValues.CONTINUE.contentEqualsIgnoreCase(expectHeader)) { + future.setDontWriteBodyBecauseExpectContinue(true); + } + return future; + } + + public void writeRequest(NettyResponseFuture future, Channel channel) { + NettyRequest nettyRequest = future.getNettyRequest(); + HttpRequest httpRequest = nettyRequest.getHttpRequest(); + AsyncHandler asyncHandler = future.getAsyncHandler(); + + // if the channel is dead because it was pooled and the remote server decided to + // close it, + // we just let it go and the channelInactive do its work + if (!Channels.isChannelActive(channel)) { + return; + } + + try { + if (asyncHandler instanceof TransferCompletionHandler) { + configureTransferAdapter(asyncHandler, httpRequest); + } + + boolean writeBody = !future.isDontWriteBodyBecauseExpectContinue() && httpRequest.method() != HttpMethod.CONNECT && nettyRequest.getBody() != null; + if (!future.isHeadersAlreadyWrittenOnContinue()) { + try { + asyncHandler.onRequestSend(nettyRequest); + } catch (Exception e) { + LOGGER.error("onRequestSend crashed", e); + abort(channel, future, e); + return; + } + + // if the request has a body, we want to track progress + if (writeBody) { + // FIXME does this really work??? the promise is for the request without body!!! + ChannelProgressivePromise promise = channel.newProgressivePromise(); + ChannelFuture f = channel.write(httpRequest, promise); + f.addListener(new WriteProgressListener(future, true, 0L)); + } else { + // we can just track write completion + ChannelPromise promise = channel.newPromise(); + ChannelFuture f = channel.writeAndFlush(httpRequest, promise); + f.addListener(new WriteCompleteListener(future)); + } + } + + if (writeBody) { + nettyRequest.getBody().write(channel, future); + } + + // don't bother scheduling read timeout if channel became invalid + if (Channels.isChannelActive(channel)) { + scheduleReadTimeout(future); + } + + } catch (Exception e) { + LOGGER.error("Can't write request", e); + abort(channel, future, e); + } + } + + private static void configureTransferAdapter(AsyncHandler handler, HttpRequest httpRequest) { + HttpHeaders h = new DefaultHttpHeaders().set(httpRequest.headers()); + ((TransferCompletionHandler) handler).headers(h); + } + + private void scheduleRequestTimeout(NettyResponseFuture nettyResponseFuture, + InetSocketAddress originalRemoteAddress) { + nettyResponseFuture.touch(); + TimeoutsHolder timeoutsHolder = new TimeoutsHolder(nettyTimer, nettyResponseFuture, this, config, + originalRemoteAddress); + nettyResponseFuture.setTimeoutsHolder(timeoutsHolder); + } + + private static void scheduleReadTimeout(NettyResponseFuture nettyResponseFuture) { + TimeoutsHolder timeoutsHolder = nettyResponseFuture.getTimeoutsHolder(); + if (timeoutsHolder != null) { + // on very fast requests, it's entirely possible that the response has already + // been completed + // by the time we try to schedule the read timeout + nettyResponseFuture.touch(); + timeoutsHolder.startReadTimeout(); + } + } + + public void abort(Channel channel, NettyResponseFuture future, Throwable t) { + if (channel != null) { + if (channel.isActive()) { + channelManager.closeChannel(channel); + } + } + + if (!future.isDone()) { + future.setChannelState(ChannelState.CLOSED); + LOGGER.debug("Aborting Future {}\n", future); + LOGGER.debug(t.getMessage(), t); + future.abort(t); + } + } + + public void handleUnexpectedClosedChannel(Channel channel, NettyResponseFuture future) { + if (Channels.isActiveTokenSet(channel)) { + if (future.isDone()) { + channelManager.closeChannel(channel); + } else if (future.incrementRetryAndCheck() && retry(future)) { + future.pendingException = null; + } else { + abort(channel, future, future.pendingException != null ? future.pendingException : RemotelyClosedException.INSTANCE); + } + } + } + + public boolean retry(NettyResponseFuture future) { + if (isClosed()) { + return false; + } + + if (future.isReplayPossible()) { + future.setChannelState(ChannelState.RECONNECTED); + + LOGGER.debug("Trying to recover request {}\n", future.getNettyRequest().getHttpRequest()); + try { + future.getAsyncHandler().onRetry(); + } catch (Exception e) { + LOGGER.error("onRetry crashed", e); + abort(future.channel(), future, e); + return false; + } + + try { + sendNextRequest(future.getCurrentRequest(), future); + return true; + + } catch (Exception e) { + abort(future.channel(), future, e); + return false; + } + } else { + LOGGER.debug("Unable to recover future {}\n", future); + return false; + } + } + + public boolean applyIoExceptionFiltersAndReplayRequest(NettyResponseFuture future, IOException e, Channel channel) { + + boolean replayed = false; + @SuppressWarnings({"unchecked", "rawtypes"}) + FilterContext fc = new FilterContext.FilterContextBuilder(future.getAsyncHandler(), future.getCurrentRequest()) + .ioException(e).build(); + for (IOExceptionFilter asyncFilter : config.getIoExceptionFilters()) { + try { + fc = asyncFilter.filter(fc); + requireNonNull(fc, "filterContext"); + } catch (FilterException efe) { + abort(channel, future, efe); + } + } + + if (fc.replayRequest() && future.incrementRetryAndCheck() && future.isReplayPossible()) { + future.setKeepAlive(false); + replayRequest(future, fc, channel); + replayed = true; + } + return replayed; + } + + public void sendNextRequest(final Request request, final NettyResponseFuture future) { + sendRequest(request, future.getAsyncHandler(), future); + } + + private static void validateWebSocketRequest(Request request, AsyncHandler asyncHandler) { + Uri uri = request.getUri(); + boolean isWs = uri.isWebSocket(); + if (asyncHandler instanceof WebSocketUpgradeHandler) { + if (!isWs) { + throw new IllegalArgumentException("WebSocketUpgradeHandler but scheme isn't ws or wss: " + uri.getScheme()); + } else if (!request.getMethod().equals(GET) && !request.getMethod().equals(CONNECT)) { + throw new IllegalArgumentException("WebSocketUpgradeHandler but method isn't GET or CONNECT: " + request.getMethod()); + } + } else if (isWs) { + throw new IllegalArgumentException("No WebSocketUpgradeHandler but scheme is " + uri.getScheme()); + } + } + + private Channel pollPooledChannel(Request request, ProxyServer proxy, AsyncHandler asyncHandler) { + try { + asyncHandler.onConnectionPoolAttempt(); + } catch (Exception e) { + LOGGER.error("onConnectionPoolAttempt crashed", e); + } + + Uri uri = request.getUri(); + String virtualHost = request.getVirtualHost(); + final Channel channel = channelManager.poll(uri, virtualHost, proxy, request.getChannelPoolPartitioning()); + + if (channel != null) { + LOGGER.debug("Using pooled Channel '{}' for '{}' to '{}'", channel, request.getMethod(), uri); + } + return channel; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public void replayRequest(final NettyResponseFuture future, FilterContext fc, Channel channel) { + Request newRequest = fc.getRequest(); + future.setAsyncHandler(fc.getAsyncHandler()); + future.setChannelState(ChannelState.NEW); + future.touch(); + + LOGGER.debug("\n\nReplaying Request {}\n for Future {}\n", newRequest, future); + try { + future.getAsyncHandler().onRetry(); + } catch (Exception e) { + LOGGER.error("onRetry crashed", e); + abort(channel, future, e); + return; + } + + channelManager.drainChannelAndOffer(channel, future); + sendNextRequest(newRequest, future); + } + + public boolean isClosed() { + return clientState.isClosed(); + } + + public void drainChannelAndExecuteNextRequest(final Channel channel, final NettyResponseFuture future, Request nextRequest) { + Channels.setAttribute(channel, new OnLastHttpContentCallback(future) { + @Override + public void call() { + sendNextRequest(nextRequest, future); + } + }); + } + + public void drainChannelAndExecuteNextRequest(final Channel channel, final NettyResponseFuture future, Request nextRequest, Future whenHandshaked) { + Channels.setAttribute(channel, new OnLastHttpContentCallback(future) { + @Override + public void call() { + whenHandshaked.addListener(f -> { + if (f.isSuccess()) { + sendNextRequest(nextRequest, future); + } else { + future.abort(f.cause()); + } + } + ); + } + }); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/request/WriteCompleteListener.java b/client/src/main/java/org/asynchttpclient/netty/request/WriteCompleteListener.java new file mode 100644 index 0000000000..1ba2530715 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/WriteCompleteListener.java @@ -0,0 +1,32 @@ +/* + * 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.netty.request; + +import io.netty.channel.ChannelFuture; +import io.netty.util.concurrent.GenericFutureListener; +import org.asynchttpclient.netty.NettyResponseFuture; + +public class WriteCompleteListener extends WriteListener implements GenericFutureListener { + + WriteCompleteListener(NettyResponseFuture future) { + super(future, true); + } + + @Override + public void operationComplete(ChannelFuture future) { + operationComplete(future.channel(), future.cause()); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/request/WriteListener.java b/client/src/main/java/org/asynchttpclient/netty/request/WriteListener.java new file mode 100644 index 0000000000..95f8d4af85 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/WriteListener.java @@ -0,0 +1,79 @@ +/* + * 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.netty.request; + +import io.netty.channel.Channel; +import org.asynchttpclient.handler.ProgressAsyncHandler; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.channel.ChannelState; +import org.asynchttpclient.netty.channel.Channels; +import org.asynchttpclient.netty.future.StackTraceInspector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLException; +import java.nio.channels.ClosedChannelException; + +public abstract class WriteListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(WriteListener.class); + protected final NettyResponseFuture future; + final ProgressAsyncHandler progressAsyncHandler; + final boolean notifyHeaders; + + WriteListener(NettyResponseFuture future, boolean notifyHeaders) { + this.future = future; + progressAsyncHandler = future.getAsyncHandler() instanceof ProgressAsyncHandler ? (ProgressAsyncHandler) future.getAsyncHandler() : null; + this.notifyHeaders = notifyHeaders; + } + + private void abortOnThrowable(Channel channel, Throwable cause) { + if (future.getChannelState() == ChannelState.POOLED && (cause instanceof IllegalStateException || + cause instanceof ClosedChannelException || + cause instanceof SSLException || + StackTraceInspector.recoverOnReadOrWriteException(cause))) { + LOGGER.debug("Write exception on pooled channel, letting retry trigger", cause); + } else { + future.abort(cause); + } + Channels.silentlyCloseChannel(channel); + } + + void operationComplete(Channel channel, Throwable cause) { + future.touch(); + + // The write operation failed. If the channel was pooled, it means it got asynchronously closed. + // Let's retry a second time. + if (cause != null) { + abortOnThrowable(channel, cause); + return; + } + + if (progressAsyncHandler != null) { + // We need to make sure we aren't in the middle of an authorization process before publishing events as we will re-publish again the same event after the authorization, + // causing unpredictable behavior. + boolean startPublishing = !future.isInAuth() && !future.isInProxyAuth(); + if (startPublishing) { + + if (notifyHeaders) { + progressAsyncHandler.onHeadersWritten(); + } else { + progressAsyncHandler.onContentWritten(); + } + } + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/request/WriteProgressListener.java b/client/src/main/java/org/asynchttpclient/netty/request/WriteProgressListener.java new file mode 100755 index 0000000000..98f669eae3 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/WriteProgressListener.java @@ -0,0 +1,52 @@ +/* + * 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. + * 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; + +import io.netty.channel.ChannelProgressiveFuture; +import io.netty.channel.ChannelProgressiveFutureListener; +import org.asynchttpclient.netty.NettyResponseFuture; + +public class WriteProgressListener extends WriteListener implements ChannelProgressiveFutureListener { + + private final long expectedTotal; + private long lastProgress; + + public WriteProgressListener(NettyResponseFuture future, boolean notifyHeaders, long expectedTotal) { + super(future, notifyHeaders); + this.expectedTotal = expectedTotal; + } + + @Override + public void operationComplete(ChannelProgressiveFuture cf) { + operationComplete(cf.channel(), cf.cause()); + } + + @Override + public void operationProgressed(ChannelProgressiveFuture f, long progress, long total) { + future.touch(); + + if (progressAsyncHandler != null && !notifyHeaders) { + long lastLastProgress = lastProgress; + lastProgress = progress; + if (total < 0) { + total = expectedTotal; + } + if (progress != lastLastProgress) { + progressAsyncHandler.onContentWriteProgress(progress - lastLastProgress, progress, total); + } + } + } +} 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 new file mode 100755 index 0000000000..772baca43a --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java @@ -0,0 +1,98 @@ +/* + * 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. + * 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; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.stream.ChunkedInput; +import org.asynchttpclient.request.body.Body; + +import static java.util.Objects.requireNonNull; + +/** + * Adapts a {@link Body} to Netty's {@link ChunkedInput}. + */ +public class BodyChunkedInput implements ChunkedInput { + + public static final int DEFAULT_CHUNK_SIZE = 8 * 1024; + + private final Body body; + private final int chunkSize; + private final long contentLength; + private boolean endOfInput; + private long progress; + + BodyChunkedInput(Body body) { + this.body = requireNonNull(body, "body"); + contentLength = body.getContentLength(); + if (contentLength <= 0) { + chunkSize = DEFAULT_CHUNK_SIZE; + } else { + chunkSize = (int) Math.min(contentLength, DEFAULT_CHUNK_SIZE); + } + } + + @Override + @Deprecated + public ByteBuf readChunk(ChannelHandlerContext ctx) throws Exception { + return readChunk(ctx.alloc()); + } + + @Override + public ByteBuf readChunk(ByteBufAllocator alloc) throws Exception { + if (endOfInput) { + return null; + } + + ByteBuf buffer = alloc.buffer(chunkSize); + Body.BodyState state = body.transferTo(buffer); + progress += buffer.writerIndex(); + switch (state) { + case STOP: + endOfInput = true; + return buffer; + case SUSPEND: + // this will suspend the stream in ChunkedWriteHandler + buffer.release(); + return null; + case CONTINUE: + return buffer; + default: + throw new IllegalStateException("Unknown state: " + state); + } + } + + @Override + public boolean isEndOfInput() { + return endOfInput; + } + + @Override + public void close() throws Exception { + body.close(); + } + + @Override + public long length() { + return contentLength; + } + + @Override + public long progress() { + return progress; + } +} 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 new file mode 100755 index 0000000000..91f2b1ecfc --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java @@ -0,0 +1,95 @@ +/* + * 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. + * 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.channel.FileRegion; +import io.netty.util.AbstractReferenceCounted; +import org.asynchttpclient.request.body.RandomAccessBody; + +import java.io.IOException; +import java.nio.channels.WritableByteChannel; + +import static java.util.Objects.requireNonNull; +import static org.asynchttpclient.util.MiscUtils.closeSilently; + +/** + * Adapts a {@link RandomAccessBody} to Netty's {@link FileRegion}. + */ +class BodyFileRegion extends AbstractReferenceCounted implements FileRegion { + + private final RandomAccessBody body; + private long transferred; + + BodyFileRegion(RandomAccessBody body) { + this.body = requireNonNull(body, "body"); + } + + @Override + public long position() { + return 0; + } + + @Override + public long count() { + return body.getContentLength(); + } + + @Override + public long transfered() { + return transferred(); + } + + @Override + public long transferred() { + return transferred; + } + + @Override + public FileRegion retain() { + super.retain(); + return this; + } + + @Override + public FileRegion retain(int increment) { + super.retain(increment); + return this; + } + + @Override + public FileRegion touch() { + return this; + } + + @Override + public FileRegion touch(Object hint) { + return this; + } + + @Override + public long transferTo(WritableByteChannel target, long position) throws IOException { + long written = body.transferTo(target); + if (written > 0) { + transferred += written; + } + return written; + } + + @Override + protected void deallocate() { + closeSilently(body); + } +} 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 new file mode 100755 index 0000000000..f38ef3939d --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java @@ -0,0 +1,32 @@ +/* + * 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. + * 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.channel.Channel; +import org.asynchttpclient.netty.NettyResponseFuture; + +import java.io.IOException; + +public interface NettyBody { + + long getContentLength(); + + default CharSequence getContentTypeOverride() { + return null; + } + + void write(Channel channel, NettyResponseFuture future) throws IOException; +} 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 new file mode 100755 index 0000000000..efe337bfe8 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java @@ -0,0 +1,89 @@ +/* + * 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. + * 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.channel.Channel; +import io.netty.channel.ChannelProgressiveFuture; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.handler.stream.ChunkedWriteHandler; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.channel.ChannelManager; +import org.asynchttpclient.netty.request.WriteProgressListener; +import org.asynchttpclient.request.body.Body; +import org.asynchttpclient.request.body.RandomAccessBody; +import org.asynchttpclient.request.body.generator.BodyGenerator; +import org.asynchttpclient.request.body.generator.FeedListener; +import org.asynchttpclient.request.body.generator.FeedableBodyGenerator; + +import static org.asynchttpclient.util.MiscUtils.closeSilently; + +public class NettyBodyBody implements NettyBody { + + private final Body body; + private final AsyncHttpClientConfig config; + + public NettyBodyBody(Body body, AsyncHttpClientConfig config) { + this.body = body; + this.config = config; + } + + public Body getBody() { + return body; + } + + @Override + public long getContentLength() { + return body.getContentLength(); + } + + @Override + public void write(final Channel channel, NettyResponseFuture future) { + + Object msg; + if (body instanceof RandomAccessBody && !ChannelManager.isSslHandlerConfigured(channel.pipeline()) && !config.isDisableZeroCopy() && getContentLength() > 0) { + msg = new BodyFileRegion((RandomAccessBody) body); + + } else { + msg = new BodyChunkedInput(body); + + BodyGenerator bg = future.getTargetRequest().getBodyGenerator(); + if (bg instanceof FeedableBodyGenerator) { + final ChunkedWriteHandler chunkedWriteHandler = channel.pipeline().get(ChunkedWriteHandler.class); + ((FeedableBodyGenerator) bg).setListener(new FeedListener() { + @Override + public void onContentAdded() { + chunkedWriteHandler.resumeTransfer(); + } + + @Override + public void onError(Throwable t) { + } + }); + } + } + + channel.write(msg, channel.newProgressivePromise()) + .addListener(new WriteProgressListener(future, false, getContentLength()) { + @Override + public void operationComplete(ChannelProgressiveFuture cf) { + closeSilently(body); + super.operationComplete(cf); + } + }); + channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT, channel.voidPromise()); + } +} 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 new file mode 100755 index 0000000000..b794ab6e96 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java @@ -0,0 +1,38 @@ +/* + * 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. + * 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; +import io.netty.buffer.Unpooled; + +public class NettyByteArrayBody extends NettyDirectBody { + + private final byte[] bytes; + + public NettyByteArrayBody(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public long getContentLength() { + return bytes.length; + } + + @Override + public ByteBuf byteBuf() { + return Unpooled.wrappedBuffer(bytes); + } +} 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 0000000000..d236cdade3 --- /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/NettyByteBufferBody.java b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufferBody.java new file mode 100644 index 0000000000..5e79b54b07 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufferBody.java @@ -0,0 +1,56 @@ +/* + * 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; +import io.netty.buffer.Unpooled; + +import java.nio.ByteBuffer; + +public class NettyByteBufferBody extends NettyDirectBody { + + private final ByteBuffer bb; + private final CharSequence contentTypeOverride; + private final long length; + + public NettyByteBufferBody(ByteBuffer bb) { + this(bb, null); + } + + public NettyByteBufferBody(ByteBuffer bb, CharSequence contentTypeOverride) { + this.bb = bb; + length = bb.remaining(); + bb.mark(); + this.contentTypeOverride = contentTypeOverride; + } + + @Override + public long getContentLength() { + return length; + } + + @Override + public CharSequence getContentTypeOverride() { + return contentTypeOverride; + } + + @Override + public ByteBuf byteBuf() { + // for retry + bb.reset(); + return Unpooled.wrappedBuffer(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 new file mode 100644 index 0000000000..e852528fd7 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.java @@ -0,0 +1,47 @@ +/* + * 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. + * 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; +import io.netty.buffer.Unpooled; + +import java.util.List; + +public class NettyCompositeByteArrayBody extends NettyDirectBody { + + private final byte[][] bytes; + private final long contentLength; + + public NettyCompositeByteArrayBody(List bytes) { + this.bytes = new byte[bytes.size()][]; + bytes.toArray(this.bytes); + long l = 0; + for (byte[] b : bytes) { + l += b.length; + } + contentLength = l; + } + + @Override + public long getContentLength() { + return contentLength; + } + + @Override + public ByteBuf byteBuf() { + return Unpooled.wrappedBuffer(bytes); + } +} 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 new file mode 100644 index 0000000000..55dfcb8bc4 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.java @@ -0,0 +1,30 @@ +/* + * 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. + * 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; +import io.netty.channel.Channel; +import org.asynchttpclient.netty.NettyResponseFuture; + +public abstract class NettyDirectBody implements NettyBody { + + public abstract ByteBuf byteBuf(); + + @Override + public void write(Channel channel, NettyResponseFuture future) { + throw new UnsupportedOperationException("This kind of body is supposed to be written directly"); + } +} 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 new file mode 100755 index 0000000000..a3c40322dc --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java @@ -0,0 +1,74 @@ +/* + * 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. + * 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.channel.Channel; +import io.netty.channel.DefaultFileRegion; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.handler.stream.ChunkedNioFile; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.channel.ChannelManager; +import org.asynchttpclient.netty.request.WriteProgressListener; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; + +public class NettyFileBody implements NettyBody { + + private final File file; + private final long offset; + private final long length; + private final AsyncHttpClientConfig config; + + public NettyFileBody(File file, AsyncHttpClientConfig config) { + this(file, 0, file.length(), config); + } + + public NettyFileBody(File file, long offset, long length, AsyncHttpClientConfig config) { + if (!file.isFile()) { + throw new IllegalArgumentException(String.format("File %s is not a file or doesn't exist", file.getAbsolutePath())); + } + this.file = file; + this.offset = offset; + this.length = length; + this.config = config; + } + + public File getFile() { + return file; + } + + @Override + public long getContentLength() { + return length; + } + + @Override + public void write(Channel channel, NettyResponseFuture future) throws IOException { + @SuppressWarnings("resource") + // netty will close the FileChannel + FileChannel fileChannel = new RandomAccessFile(file, "r").getChannel(); + boolean noZeroCopy = ChannelManager.isSslHandlerConfigured(channel.pipeline()) || config.isDisableZeroCopy(); + Object body = noZeroCopy ? new ChunkedNioFile(fileChannel, offset, length, config.getChunkedFileChunkSize()) : new DefaultFileRegion(fileChannel, offset, length); + + channel.write(body, channel.newProgressivePromise()) + .addListener(new WriteProgressListener(future, false, length)); + channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT, channel.voidPromise()); + } +} 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 new file mode 100755 index 0000000000..4dba9d951a --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java @@ -0,0 +1,82 @@ +/* + * 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. + * 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.channel.Channel; +import io.netty.channel.ChannelProgressiveFuture; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.handler.stream.ChunkedStream; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.request.WriteProgressListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; + +import static org.asynchttpclient.util.MiscUtils.closeSilently; + +public class NettyInputStreamBody implements NettyBody { + + private static final Logger LOGGER = LoggerFactory.getLogger(NettyInputStreamBody.class); + + private final InputStream inputStream; + private final long contentLength; + + public NettyInputStreamBody(InputStream inputStream) { + this(inputStream, -1L); + } + + public NettyInputStreamBody(InputStream inputStream, long contentLength) { + this.inputStream = inputStream; + this.contentLength = contentLength; + } + + public InputStream getInputStream() { + return inputStream; + } + + @Override + public long getContentLength() { + return contentLength; + } + + @Override + public void write(Channel channel, NettyResponseFuture future) throws IOException { + final InputStream is = inputStream; + + if (future.isStreamConsumed()) { + if (is.markSupported()) { + is.reset(); + } else { + LOGGER.warn("Stream has already been consumed and cannot be reset"); + return; + } + } else { + future.setStreamConsumed(true); + } + + channel.write(new ChunkedStream(is), channel.newProgressivePromise()).addListener( + new WriteProgressListener(future, false, getContentLength()) { + @Override + public void operationComplete(ChannelProgressiveFuture cf) { + closeSilently(is); + super.operationComplete(cf); + } + }); + channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT, channel.voidPromise()); + } +} 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 new file mode 100755 index 0000000000..7fa23c07f3 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java @@ -0,0 +1,44 @@ +/* + * 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. + * 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.handler.codec.http.HttpHeaders; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.request.body.multipart.MultipartBody; +import org.asynchttpclient.request.body.multipart.Part; + +import java.util.List; + +import static org.asynchttpclient.request.body.multipart.MultipartUtils.newMultipartBody; + +public class NettyMultipartBody extends NettyBodyBody { + + private final String contentTypeOverride; + + public NettyMultipartBody(List parts, HttpHeaders headers, AsyncHttpClientConfig config) { + this(newMultipartBody(parts, headers), config); + } + + private NettyMultipartBody(MultipartBody body, AsyncHttpClientConfig config) { + super(body, config); + contentTypeOverride = body.getContentType(); + } + + @Override + public String getContentTypeOverride() { + return contentTypeOverride; + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/ssl/DefaultSslEngineFactory.java b/client/src/main/java/org/asynchttpclient/netty/ssl/DefaultSslEngineFactory.java new file mode 100644 index 0000000000..a96f6ffb1a --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/ssl/DefaultSslEngineFactory.java @@ -0,0 +1,94 @@ +/* + * 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.ssl; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.handler.ssl.IdentityCipherSuiteFilter; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslProvider; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import io.netty.util.ReferenceCountUtil; +import org.asynchttpclient.AsyncHttpClientConfig; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; +import java.util.Arrays; + +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; + +public class DefaultSslEngineFactory extends SslEngineFactoryBase { + + private volatile SslContext sslContext; + + private SslContext buildSslContext(AsyncHttpClientConfig config) throws SSLException { + if (config.getSslContext() != null) { + return config.getSslContext(); + } + + SslContextBuilder sslContextBuilder = SslContextBuilder.forClient() + .sslProvider(config.isUseOpenSsl() ? SslProvider.OPENSSL : SslProvider.JDK) + .sessionCacheSize(config.getSslSessionCacheSize()) + .sessionTimeout(config.getSslSessionTimeout()); + + if (isNonEmpty(config.getEnabledProtocols())) { + sslContextBuilder.protocols(config.getEnabledProtocols()); + } + + if (isNonEmpty(config.getEnabledCipherSuites())) { + sslContextBuilder.ciphers(Arrays.asList(config.getEnabledCipherSuites())); + } else if (!config.isFilterInsecureCipherSuites()) { + sslContextBuilder.ciphers(null, IdentityCipherSuiteFilter.INSTANCE_DEFAULTING_TO_SUPPORTED_CIPHERS); + } + + if (config.isUseInsecureTrustManager()) { + sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE); + } + + return configureSslContextBuilder(sslContextBuilder).build(); + } + + @Override + public SSLEngine newSslEngine(AsyncHttpClientConfig config, String peerHost, int peerPort) { + SSLEngine sslEngine = config.isDisableHttpsEndpointIdentificationAlgorithm() ? + sslContext.newEngine(ByteBufAllocator.DEFAULT) : + sslContext.newEngine(ByteBufAllocator.DEFAULT, domain(peerHost), peerPort); + configureSslEngine(sslEngine, config); + return sslEngine; + } + + @Override + public void init(AsyncHttpClientConfig config) throws SSLException { + sslContext = buildSslContext(config); + } + + @Override + public void destroy() { + ReferenceCountUtil.release(sslContext); + } + + /** + * The last step of configuring the SslContextBuilder used to create an SslContext when no context is provided in the {@link AsyncHttpClientConfig}. This defaults to no-op and + * is intended to be overridden as needed. + * + * @param builder builder with normal configuration applied + * @return builder to be used to build context (can be the same object as the input) + */ + protected SslContextBuilder configureSslContextBuilder(SslContextBuilder builder) { + // default to no op + return builder; + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/ssl/JsseSslEngineFactory.java b/client/src/main/java/org/asynchttpclient/netty/ssl/JsseSslEngineFactory.java new file mode 100644 index 0000000000..1c76eb84ed --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/ssl/JsseSslEngineFactory.java @@ -0,0 +1,37 @@ +/* + * 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.ssl; + +import org.asynchttpclient.AsyncHttpClientConfig; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +public class JsseSslEngineFactory extends SslEngineFactoryBase { + + private final SSLContext sslContext; + + public JsseSslEngineFactory(SSLContext sslContext) { + this.sslContext = sslContext; + } + + @Override + public SSLEngine newSslEngine(AsyncHttpClientConfig config, String peerHost, int peerPort) { + SSLEngine sslEngine = sslContext.createSSLEngine(domain(peerHost), peerPort); + configureSslEngine(sslEngine, config); + return sslEngine; + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/ssl/SslEngineFactoryBase.java b/client/src/main/java/org/asynchttpclient/netty/ssl/SslEngineFactoryBase.java new file mode 100644 index 0000000000..2d6e5f5eff --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/ssl/SslEngineFactoryBase.java @@ -0,0 +1,39 @@ +/* + * 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.ssl; + +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.SslEngineFactory; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; + +public abstract class SslEngineFactoryBase implements SslEngineFactory { + + protected String domain(String hostname) { + int fqdnLength = hostname.length() - 1; + return hostname.charAt(fqdnLength) == '.' ? hostname.substring(0, fqdnLength) : hostname; + } + + protected void configureSslEngine(SSLEngine sslEngine, AsyncHttpClientConfig config) { + sslEngine.setUseClientMode(true); + if (!config.isDisableHttpsEndpointIdentificationAlgorithm()) { + SSLParameters params = sslEngine.getSSLParameters(); + params.setEndpointIdentificationAlgorithm("HTTPS"); + sslEngine.setSSLParameters(params); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java b/client/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java new file mode 100755 index 0000000000..8b0d4373a1 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java @@ -0,0 +1,65 @@ +/* + * 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. + * 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.timeout; + +import io.netty.util.Timeout; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.request.NettyRequestSender; +import org.asynchttpclient.util.StringBuilderPool; + +import static org.asynchttpclient.util.DateUtils.unpreciseMillisTime; + +public class ReadTimeoutTimerTask extends TimeoutTimerTask { + + private final long readTimeout; + + ReadTimeoutTimerTask(NettyResponseFuture nettyResponseFuture, NettyRequestSender requestSender, TimeoutsHolder timeoutsHolder, long readTimeout) { + super(nettyResponseFuture, requestSender, timeoutsHolder); + this.readTimeout = readTimeout; + } + + @Override + public void run(Timeout timeout) { + if (done.getAndSet(true) || requestSender.isClosed()) { + return; + } + + if (nettyResponseFuture.isDone()) { + timeoutsHolder.cancel(); + return; + } + + long now = unpreciseMillisTime(); + + long currentReadTimeoutInstant = readTimeout + nettyResponseFuture.getLastTouch(); + long durationBeforeCurrentReadTimeout = currentReadTimeoutInstant - now; + + if (durationBeforeCurrentReadTimeout <= 0L) { + // idleConnectTimeout reached + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder().append("Read timeout to "); + appendRemoteAddress(sb); + String message = sb.append(" after ").append(readTimeout).append(" ms").toString(); + long durationSinceLastTouch = now - nettyResponseFuture.getLastTouch(); + expire(message, durationSinceLastTouch); + // cancel request timeout sibling + timeoutsHolder.cancel(); + + } else { + done.set(false); + timeoutsHolder.startReadTimeout(this); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java b/client/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java new file mode 100755 index 0000000000..74c5d0197a --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java @@ -0,0 +1,56 @@ +/* + * 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. + * 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.timeout; + +import io.netty.util.Timeout; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.request.NettyRequestSender; +import org.asynchttpclient.util.StringBuilderPool; + +import static org.asynchttpclient.util.DateUtils.unpreciseMillisTime; + +public class RequestTimeoutTimerTask extends TimeoutTimerTask { + + private final long requestTimeout; + + RequestTimeoutTimerTask(NettyResponseFuture nettyResponseFuture, + NettyRequestSender requestSender, + TimeoutsHolder timeoutsHolder, + long requestTimeout) { + super(nettyResponseFuture, requestSender, timeoutsHolder); + this.requestTimeout = requestTimeout; + } + + @Override + public void run(Timeout timeout) { + if (done.getAndSet(true) || requestSender.isClosed()) { + return; + } + + // in any case, cancel possible readTimeout sibling + timeoutsHolder.cancel(); + + if (nettyResponseFuture.isDone()) { + return; + } + + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder().append("Request timeout to "); + appendRemoteAddress(sb); + String message = sb.append(" after ").append(requestTimeout).append(" ms").toString(); + long age = unpreciseMillisTime() - nettyResponseFuture.getStart(); + expire(message, age); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java b/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java new file mode 100755 index 0000000000..3c9a3675ea --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java @@ -0,0 +1,66 @@ +/* + * 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. + * 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.timeout; + +import io.netty.util.TimerTask; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.request.NettyRequestSender; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class TimeoutTimerTask implements TimerTask { + + private static final Logger LOGGER = LoggerFactory.getLogger(TimeoutTimerTask.class); + + protected final AtomicBoolean done = new AtomicBoolean(); + protected final NettyRequestSender requestSender; + final TimeoutsHolder timeoutsHolder; + volatile NettyResponseFuture nettyResponseFuture; + + TimeoutTimerTask(NettyResponseFuture nettyResponseFuture, NettyRequestSender requestSender, TimeoutsHolder timeoutsHolder) { + this.nettyResponseFuture = nettyResponseFuture; + this.requestSender = requestSender; + this.timeoutsHolder = timeoutsHolder; + } + + void expire(String message, long time) { + LOGGER.debug("{} for {} after {} ms", message, nettyResponseFuture, time); + requestSender.abort(nettyResponseFuture.channel(), nettyResponseFuture, new TimeoutException(message)); + } + + /** + * When the timeout is cancelled, it could still be referenced for quite some time in the Timer. Holding a reference to the future might mean holding a reference to the + * channel, and heavy objects such as SslEngines + */ + public void clean() { + if (done.compareAndSet(false, true)) { + nettyResponseFuture = null; + } + } + + void appendRemoteAddress(StringBuilder sb) { + InetSocketAddress remoteAddress = timeoutsHolder.remoteAddress(); + sb.append(remoteAddress.getHostString()); + if (!remoteAddress.isUnresolved()) { + sb.append('/').append(remoteAddress.getAddress().getHostAddress()); + } + sb.append(':').append(remoteAddress.getPort()); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java b/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java new file mode 100755 index 0000000000..acce84b6d3 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java @@ -0,0 +1,115 @@ +/* + * 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. + * 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.timeout; + +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.Request; +import org.asynchttpclient.netty.NettyResponseFuture; +import org.asynchttpclient.netty.request.NettyRequestSender; + +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.asynchttpclient.util.DateUtils.unpreciseMillisTime; + +public class TimeoutsHolder { + + private final Timeout requestTimeout; + private final AtomicBoolean cancelled = new AtomicBoolean(); + private final Timer nettyTimer; + private final NettyRequestSender requestSender; + private final long requestTimeoutMillisTime; + private final long readTimeoutValue; + private volatile Timeout readTimeout; + private final NettyResponseFuture nettyResponseFuture; + private volatile InetSocketAddress remoteAddress; + + public TimeoutsHolder(Timer nettyTimer, NettyResponseFuture nettyResponseFuture, NettyRequestSender requestSender, + AsyncHttpClientConfig config, InetSocketAddress originalRemoteAddress) { + this.nettyTimer = nettyTimer; + this.nettyResponseFuture = nettyResponseFuture; + this.requestSender = requestSender; + remoteAddress = originalRemoteAddress; + + final Request targetRequest = nettyResponseFuture.getTargetRequest(); + + final long readTimeoutInMs = targetRequest.getReadTimeout().toMillis(); + readTimeoutValue = readTimeoutInMs == 0 ? config.getReadTimeout().toMillis() : readTimeoutInMs; + + long requestTimeoutInMs = targetRequest.getRequestTimeout().toMillis(); + if (requestTimeoutInMs == 0) { + requestTimeoutInMs = config.getRequestTimeout().toMillis(); + } + + if (requestTimeoutInMs > -1) { + requestTimeoutMillisTime = unpreciseMillisTime() + requestTimeoutInMs; + requestTimeout = newTimeout(new RequestTimeoutTimerTask(nettyResponseFuture, requestSender, this, requestTimeoutInMs), requestTimeoutInMs); + } else { + requestTimeoutMillisTime = -1L; + requestTimeout = null; + } + } + + public void setResolvedRemoteAddress(InetSocketAddress address) { + remoteAddress = address; + } + + InetSocketAddress remoteAddress() { + return remoteAddress; + } + + public void startReadTimeout() { + if (readTimeoutValue != -1) { + startReadTimeout(null); + } + } + + void startReadTimeout(ReadTimeoutTimerTask task) { + if (requestTimeout == null || !requestTimeout.isExpired() && readTimeoutValue < requestTimeoutMillisTime - unpreciseMillisTime()) { + // only schedule a new readTimeout if the requestTimeout doesn't happen first + if (task == null) { + // first call triggered from outside (else is read timeout is re-scheduling itself) + task = new ReadTimeoutTimerTask(nettyResponseFuture, requestSender, this, readTimeoutValue); + } + readTimeout = newTimeout(task, readTimeoutValue); + + } else if (task != null) { + // read timeout couldn't re-scheduling itself, clean up + task.clean(); + } + } + + public void cancel() { + if (cancelled.compareAndSet(false, true)) { + if (requestTimeout != null) { + requestTimeout.cancel(); + ((TimeoutTimerTask) requestTimeout.task()).clean(); + } + if (readTimeout != null) { + readTimeout.cancel(); + ((TimeoutTimerTask) readTimeout.task()).clean(); + } + } + } + + private Timeout newTimeout(TimerTask task, long delay) { + return requestSender.isClosed() ? null : nettyTimer.newTimeout(task, delay, TimeUnit.MILLISECONDS); + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java b/client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java new file mode 100755 index 0000000000..2329edacf9 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java @@ -0,0 +1,350 @@ +/* + * 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. + * 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.ws; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; +import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame; +import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; +import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.ImmediateEventExecutor; +import org.asynchttpclient.netty.channel.Channels; +import org.asynchttpclient.ws.WebSocket; +import org.asynchttpclient.ws.WebSocketListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; + +import static io.netty.buffer.Unpooled.wrappedBuffer; + +public final class NettyWebSocket implements WebSocket { + + private static final Logger LOGGER = LoggerFactory.getLogger(NettyWebSocket.class); + + private final Channel channel; + private final HttpHeaders upgradeHeaders; + private final Collection listeners; + private FragmentedFrameType expectedFragmentedFrameType; + // no need for volatile because only mutated in IO thread + private boolean ready; + private List bufferedFrames; + + public NettyWebSocket(Channel channel, HttpHeaders upgradeHeaders) { + this(channel, upgradeHeaders, new ConcurrentLinkedQueue<>()); + } + + private NettyWebSocket(Channel channel, HttpHeaders upgradeHeaders, Collection listeners) { + this.channel = channel; + this.upgradeHeaders = upgradeHeaders; + this.listeners = listeners; + } + + @Override + public HttpHeaders getUpgradeHeaders() { + return upgradeHeaders; + } + + @Override + public SocketAddress getRemoteAddress() { + return channel.remoteAddress(); + } + + @Override + public SocketAddress getLocalAddress() { + return channel.localAddress(); + } + + @Override + public Future sendTextFrame(String message) { + return sendTextFrame(message, true, 0); + } + + @Override + public Future sendTextFrame(String payload, boolean finalFragment, int rsv) { + return channel.writeAndFlush(new TextWebSocketFrame(finalFragment, rsv, payload)); + } + + @Override + public Future sendTextFrame(ByteBuf payload, boolean finalFragment, int rsv) { + return channel.writeAndFlush(new TextWebSocketFrame(finalFragment, rsv, payload)); + } + + @Override + public Future sendBinaryFrame(byte[] payload) { + return sendBinaryFrame(payload, true, 0); + } + + @Override + public Future sendBinaryFrame(byte[] payload, boolean finalFragment, int rsv) { + return sendBinaryFrame(wrappedBuffer(payload), finalFragment, rsv); + } + + @Override + public Future sendBinaryFrame(ByteBuf payload, boolean finalFragment, int rsv) { + return channel.writeAndFlush(new BinaryWebSocketFrame(finalFragment, rsv, payload)); + } + + @Override + public Future sendContinuationFrame(String payload, boolean finalFragment, int rsv) { + return channel.writeAndFlush(new ContinuationWebSocketFrame(finalFragment, rsv, payload)); + } + + @Override + public Future sendContinuationFrame(byte[] payload, boolean finalFragment, int rsv) { + return sendContinuationFrame(wrappedBuffer(payload), finalFragment, rsv); + } + + @Override + public Future sendContinuationFrame(ByteBuf payload, boolean finalFragment, int rsv) { + return channel.writeAndFlush(new ContinuationWebSocketFrame(finalFragment, rsv, payload)); + } + + @Override + public Future sendPingFrame() { + return channel.writeAndFlush(new PingWebSocketFrame()); + } + + @Override + public Future sendPingFrame(byte[] payload) { + return sendPingFrame(wrappedBuffer(payload)); + } + + @Override + public Future sendPingFrame(ByteBuf payload) { + return channel.writeAndFlush(new PingWebSocketFrame(payload)); + } + + @Override + public Future sendPongFrame() { + return channel.writeAndFlush(new PongWebSocketFrame()); + } + + @Override + public Future sendPongFrame(byte[] payload) { + return sendPongFrame(wrappedBuffer(payload)); + } + + @Override + public Future sendPongFrame(ByteBuf payload) { + return channel.writeAndFlush(new PongWebSocketFrame(wrappedBuffer(payload))); + } + + @Override + public Future sendCloseFrame() { + return sendCloseFrame(1000, "normal closure"); + } + + @Override + public Future sendCloseFrame(int statusCode, String reasonText) { + if (channel.isOpen()) { + return channel.writeAndFlush(new CloseWebSocketFrame(statusCode, reasonText)); + } + return ImmediateEventExecutor.INSTANCE.newSucceededFuture(null); + } + + @Override + public boolean isOpen() { + return channel.isOpen(); + } + + @Override + public WebSocket addWebSocketListener(WebSocketListener l) { + listeners.add(l); + return this; + } + + @Override + public WebSocket removeWebSocketListener(WebSocketListener l) { + listeners.remove(l); + return this; + } + + // INTERNAL, NOT FOR PUBLIC USAGE!!! + + public boolean isReady() { + return ready; + } + + public void bufferFrame(WebSocketFrame frame) { + if (bufferedFrames == null) { + bufferedFrames = new ArrayList<>(1); + } + frame.retain(); + bufferedFrames.add(frame); + } + + private void releaseBufferedFrames() { + if (bufferedFrames != null) { + for (WebSocketFrame frame : bufferedFrames) { + frame.release(); + } + bufferedFrames = null; + } + } + + public void processBufferedFrames() { + ready = true; + if (bufferedFrames != null) { + try { + for (WebSocketFrame frame : bufferedFrames) { + handleFrame(frame); + } + } finally { + releaseBufferedFrames(); + } + bufferedFrames = null; + } + } + + public void handleFrame(WebSocketFrame frame) { + if (frame instanceof TextWebSocketFrame) { + onTextFrame((TextWebSocketFrame) frame); + + } else if (frame instanceof BinaryWebSocketFrame) { + onBinaryFrame((BinaryWebSocketFrame) frame); + + } else if (frame instanceof CloseWebSocketFrame) { + Channels.setDiscard(channel); + CloseWebSocketFrame closeFrame = (CloseWebSocketFrame) frame; + onClose(closeFrame.statusCode(), closeFrame.reasonText()); + Channels.silentlyCloseChannel(channel); + + } else if (frame instanceof PingWebSocketFrame) { + onPingFrame((PingWebSocketFrame) frame); + + } else if (frame instanceof PongWebSocketFrame) { + onPongFrame((PongWebSocketFrame) frame); + + } else if (frame instanceof ContinuationWebSocketFrame) { + onContinuationFrame((ContinuationWebSocketFrame) frame); + } + } + + public void onError(Throwable t) { + try { + for (WebSocketListener listener : listeners) { + try { + listener.onError(t); + } catch (Throwable t2) { + LOGGER.error("WebSocketListener.onError crash", t2); + } + } + } finally { + releaseBufferedFrames(); + } + } + + public void onClose(int code, String reason) { + try { + for (WebSocketListener listener : listeners) { + try { + listener.onClose(this, code, reason); + } catch (Throwable t) { + listener.onError(t); + } + } + listeners.clear(); + } finally { + releaseBufferedFrames(); + } + } + + @Override + public String toString() { + return "NettyWebSocket{channel=" + channel + '}'; + } + + private void onBinaryFrame(BinaryWebSocketFrame frame) { + if (expectedFragmentedFrameType == null && !frame.isFinalFragment()) { + expectedFragmentedFrameType = FragmentedFrameType.BINARY; + } + onBinaryFrame0(frame); + } + + private void onBinaryFrame0(WebSocketFrame frame) { + byte[] bytes = ByteBufUtil.getBytes(frame.content()); + for (WebSocketListener listener : listeners) { + listener.onBinaryFrame(bytes, frame.isFinalFragment(), frame.rsv()); + } + } + + private void onTextFrame(TextWebSocketFrame frame) { + if (expectedFragmentedFrameType == null && !frame.isFinalFragment()) { + expectedFragmentedFrameType = FragmentedFrameType.TEXT; + } + onTextFrame0(frame); + } + + private void onTextFrame0(WebSocketFrame frame) { + for (WebSocketListener listener : listeners) { + listener.onTextFrame(frame.content().toString(StandardCharsets.UTF_8), frame.isFinalFragment(), frame.rsv()); + } + } + + private void onContinuationFrame(ContinuationWebSocketFrame frame) { + if (expectedFragmentedFrameType == null) { + LOGGER.warn("Received continuation frame without an original text or binary frame, ignoring"); + return; + } + try { + switch (expectedFragmentedFrameType) { + case BINARY: + onBinaryFrame0(frame); + break; + case TEXT: + onTextFrame0(frame); + break; + default: + throw new IllegalArgumentException("Unknown FragmentedFrameType " + expectedFragmentedFrameType); + } + } finally { + if (frame.isFinalFragment()) { + expectedFragmentedFrameType = null; + } + } + } + + private void onPingFrame(PingWebSocketFrame frame) { + byte[] bytes = ByteBufUtil.getBytes(frame.content()); + for (WebSocketListener listener : listeners) { + listener.onPingFrame(bytes); + } + } + + private void onPongFrame(PongWebSocketFrame frame) { + byte[] bytes = ByteBufUtil.getBytes(frame.content()); + for (WebSocketListener listener : listeners) { + listener.onPongFrame(bytes); + } + } + + private enum FragmentedFrameType { + TEXT, BINARY + } +} diff --git a/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java b/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java new file mode 100644 index 0000000000..c2338c46a0 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java @@ -0,0 +1,1586 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * 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 + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +// fork from Apache HttpComponents +package org.asynchttpclient.ntlm; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Base64; +import java.util.Locale; + +import static java.nio.charset.StandardCharsets.US_ASCII; + +/** + * Provides an implementation for NTLMv1, NTLMv2, and NTLM2 Session forms of the NTLM + * authentication protocol. + * + * @since 4.1 + */ +@SuppressWarnings("unused") +public final class NtlmEngine { + + public static final NtlmEngine INSTANCE = new NtlmEngine(); + + /** + * Unicode encoding + */ + private static final Charset UNICODE_LITTLE_UNMARKED = StandardCharsets.UTF_16LE; + + private static final byte[] MAGIC_CONSTANT = "KGS!@#$%".getBytes(US_ASCII); + + // Flags we use; descriptions according to: + // http://davenport.sourceforge.net/ntlm.html + // and + // http://msdn.microsoft.com/en-us/library/cc236650%28v=prot.20%29.aspx + private static final int FLAG_REQUEST_UNICODE_ENCODING = 0x00000001; // Unicode string encoding requested + private static final int FLAG_REQUEST_TARGET = 0x00000004; // Requests target field + private static final int FLAG_REQUEST_SIGN = 0x00000010; // Requests all messages have a signature attached, in NEGOTIATE message. + private static final int FLAG_REQUEST_SEAL = 0x00000020; // Request key exchange for message confidentiality in NEGOTIATE message. MUST be used in conjunction with 56BIT. + private static final int FLAG_REQUEST_LAN_MANAGER_KEY = 0x00000080; // Request Lan Manager key instead of user session key + private static final int FLAG_REQUEST_NTLMv1 = 0x00000200; // Request NTLMv1 security. MUST be set in NEGOTIATE and CHALLENGE both + private static final int FLAG_DOMAIN_PRESENT = 0x00001000; // Domain is present in message + private static final int FLAG_WORKSTATION_PRESENT = 0x00002000; // Workstation is present in message + private static final int FLAG_REQUEST_ALWAYS_SIGN = 0x00008000; // Requests a signature block on all messages. Overridden by REQUEST_SIGN and REQUEST_SEAL. + private static final int FLAG_REQUEST_NTLM2_SESSION = 0x00080000; // From server in challenge, requesting NTLM2 session security + private static final int FLAG_REQUEST_VERSION = 0x02000000; // Request protocol version + private static final int FLAG_TARGETINFO_PRESENT = 0x00800000; // From server in challenge message, indicating targetinfo is present + private static final int FLAG_REQUEST_128BIT_KEY_EXCH = 0x20000000; // Request explicit 128-bit key exchange + private static final int FLAG_REQUEST_EXPLICIT_KEY_EXCH = 0x40000000; // Request explicit key exchange + private static final int FLAG_REQUEST_56BIT_ENCRYPTION = 0x80000000; // Must be used in conjunction with SEAL + + /** + * Secure random generator + */ + private static final @Nullable SecureRandom RND_GEN; + + static { + SecureRandom rnd = null; + try { + rnd = SecureRandom.getInstance("SHA1PRNG"); + } catch (final Exception ignore) { + } + RND_GEN = rnd; + } + + /** + * The signature string as bytes in the default encoding + */ + private static final byte[] SIGNATURE; + + static { + final byte[] bytesWithoutNull = "NTLMSSP".getBytes(US_ASCII); + SIGNATURE = new byte[bytesWithoutNull.length + 1]; + System.arraycopy(bytesWithoutNull, 0, SIGNATURE, 0, bytesWithoutNull.length); + SIGNATURE[bytesWithoutNull.length] = 0x00; + } + + private static final String TYPE_1_MESSAGE = new Type1Message().getResponse(); + + /** + * Creates the type 3 message using the given server nonce. The type 3 + * message includes all the information for authentication, host, domain, + * username and the result of encrypting the nonce sent by the server using + * the user's password as the key. + * + * @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. + * @param nonce the 8 byte array the server sent. + * @return The type 3 message. + * @throws NtlmEngineException If {@encrypt(byte[],byte[])} fails. + */ + private static String getType3Message(final String user, final String password, final String host, final String domain, final byte[] nonce, + final int type2Flags, final @Nullable String target, final byte @Nullable [] targetInformation) { + return new Type3Message(domain, host, user, password, nonce, type2Flags, target, targetInformation).getResponse(); + } + + /** + * Strip dot suffix from a name + */ + private static String stripDotSuffix(final String value) { + final int index = value.indexOf('.'); + if (index != -1) { + return value.substring(0, index); + } + return value; + } + + /** + * Convert host to standard form + */ + @Contract(value = "!null -> !null", pure = true) + private static @Nullable String convertHost(final String host) { + return host != null ? stripDotSuffix(host).toUpperCase() : null; + } + + /** + * Convert domain to standard form + */ + @Contract(value = "!null -> !null", pure = true) + private static @Nullable String convertDomain(final String domain) { + return domain != null ? stripDotSuffix(domain).toUpperCase() : null; + } + + private static int readULong(final byte[] src, final int index) { + if (src.length < index + 4) { + throw new NtlmEngineException("NTLM authentication - buffer too small for DWORD"); + } + return src[index] & 0xff | (src[index + 1] & 0xff) << 8 | (src[index + 2] & 0xff) << 16 | (src[index + 3] & 0xff) << 24; + } + + private static int readUShort(final byte[] src, final int index) { + if (src.length < index + 2) { + throw new NtlmEngineException("NTLM authentication - buffer too small for WORD"); + } + return src[index] & 0xff | (src[index + 1] & 0xff) << 8; + } + + private static byte[] readSecurityBuffer(final byte[] src, final int index) { + final int length = readUShort(src, index); + final int offset = readULong(src, index + 4); + if (src.length < offset + length) { + throw new NtlmEngineException("NTLM authentication - buffer too small for data item"); + } + final byte[] buffer = new byte[length]; + System.arraycopy(src, offset, buffer, 0, length); + return buffer; + } + + /** + * Calculate a challenge block + */ + private static byte[] makeRandomChallenge() { + if (RND_GEN == null) { + throw new NtlmEngineException("Random generator not available"); + } + final byte[] rval = new byte[8]; + synchronized (RND_GEN) { + RND_GEN.nextBytes(rval); + } + return rval; + } + + /** + * Calculate a 16-byte secondary key + */ + private static byte[] makeSecondaryKey() { + if (RND_GEN == null) { + throw new NtlmEngineException("Random generator not available"); + } + final byte[] rval = new byte[16]; + synchronized (RND_GEN) { + RND_GEN.nextBytes(rval); + } + return rval; + } + + private static class CipherGen { + + protected final String domain; + protected final String user; + protected final String password; + protected final byte[] challenge; + protected final @Nullable String target; + protected final byte @Nullable [] targetInformation; + + // Information we can generate but may be passed in (for testing) + protected byte @Nullable [] clientChallenge; + protected byte @Nullable [] clientChallenge2; + protected byte @Nullable [] secondaryKey; + protected byte @Nullable [] timestamp; + + // Stuff we always generate + protected byte @Nullable [] lmHash; + protected byte @Nullable [] lmResponse; + protected byte @Nullable [] ntlmHash; + protected byte @Nullable [] ntlmResponse; + protected byte @Nullable [] ntlmv2Hash; + protected byte @Nullable [] lmv2Hash; + protected byte @Nullable [] lmv2Response; + protected byte @Nullable [] ntlmv2Blob; + protected byte @Nullable [] ntlmv2Response; + protected byte @Nullable [] ntlm2SessionResponse; + protected byte @Nullable [] lm2SessionResponse; + protected byte @Nullable [] lmUserSessionKey; + protected byte @Nullable [] ntlmUserSessionKey; + protected byte @Nullable [] ntlmv2UserSessionKey; + protected byte @Nullable [] ntlm2SessionResponseUserSessionKey; + protected byte @Nullable [] lanManagerSessionKey; + + CipherGen(final String domain, final String user, final String password, final byte[] challenge, final @Nullable String target, + final byte @Nullable [] targetInformation, final byte @Nullable [] clientChallenge, final byte @Nullable [] clientChallenge2, + final byte @Nullable [] secondaryKey, final byte @Nullable [] timestamp) { + this.domain = domain; + this.target = target; + this.user = user; + this.password = password; + this.challenge = challenge; + this.targetInformation = targetInformation; + this.clientChallenge = clientChallenge; + this.clientChallenge2 = clientChallenge2; + this.secondaryKey = secondaryKey; + this.timestamp = timestamp; + } + + CipherGen(final String domain, final String user, final String password, final byte[] challenge, final @Nullable String target, + final byte @Nullable [] targetInformation) { + this(domain, user, password, challenge, target, targetInformation, null, null, null, null); + } + + /** + * Calculate and return client challenge + */ + public byte[] getClientChallenge() { + if (clientChallenge == null) { + clientChallenge = makeRandomChallenge(); + } + return clientChallenge; + } + + /** + * Calculate and return second client challenge + */ + public byte[] getClientChallenge2() { + if (clientChallenge2 == null) { + clientChallenge2 = makeRandomChallenge(); + } + return clientChallenge2; + } + + /** + * Calculate and return random secondary key + */ + public byte[] getSecondaryKey() { + if (secondaryKey == null) { + secondaryKey = makeSecondaryKey(); + } + return secondaryKey; + } + + /** + * Calculate and return the LMHash + */ + public byte[] getLMHash() { + if (lmHash == null) { + lmHash = lmHash(password); + } + return lmHash; + } + + /** + * Calculate and return the LMResponse + */ + public byte[] getLMResponse() { + if (lmResponse == null) { + lmResponse = lmResponse(getLMHash(), challenge); + } + return lmResponse; + } + + /** + * Calculate and return the NTLMHash + */ + public byte[] getNTLMHash() { + if (ntlmHash == null) { + ntlmHash = ntlmHash(password); + } + return ntlmHash; + } + + /** + * Calculate and return the NTLMResponse + */ + public byte[] getNTLMResponse() { + if (ntlmResponse == null) { + ntlmResponse = lmResponse(getNTLMHash(), challenge); + } + return ntlmResponse; + } + + /** + * Calculate the LMv2 hash + */ + public byte[] getLMv2Hash() { + if (lmv2Hash == null) { + lmv2Hash = lmv2Hash(domain, user, getNTLMHash()); + } + return lmv2Hash; + } + + /** + * Calculate the NTLMv2 hash + */ + public byte[] getNTLMv2Hash() { + if (ntlmv2Hash == null) { + ntlmv2Hash = ntlmv2Hash(domain, user, getNTLMHash()); + } + return ntlmv2Hash; + } + + /** + * Calculate a timestamp + */ + public byte[] getTimestamp() { + if (timestamp == null) { + long time = System.currentTimeMillis(); + time += 11644473600000L; // milliseconds from January 1, 1601 -> epoch. + time *= 10000; // tenths of a microsecond. + // convert to little-endian byte array. + timestamp = new byte[8]; + for (int i = 0; i < 8; i++) { + timestamp[i] = (byte) time; + time >>>= 8; + } + } + return timestamp; + } + + /** + * Calculate the NTLMv2Blob + * + * @param targetInformation this parameter is the same object as the field targetInformation, + * but guaranteed to be not null. This is done to satisfy NullAway requirements + */ + public byte[] getNTLMv2Blob(byte[] targetInformation) { + if (ntlmv2Blob == null) { + ntlmv2Blob = createBlob(getClientChallenge2(), targetInformation, getTimestamp()); + } + return ntlmv2Blob; + } + + /** + * Calculate the NTLMv2Response + * + * @param targetInformation this parameter is the same object as the field targetInformation, + * but guaranteed to be not null. This is done to satisfy NullAway requirements + */ + public byte[] getNTLMv2Response(byte[] targetInformation) { + if (ntlmv2Response == null) { + ntlmv2Response = lmv2Response(getNTLMv2Hash(), challenge, getNTLMv2Blob(targetInformation)); + } + return ntlmv2Response; + } + + /** + * Calculate the LMv2Response + */ + public byte[] getLMv2Response() { + if (lmv2Response == null) { + lmv2Response = lmv2Response(getLMv2Hash(), challenge, getClientChallenge()); + } + return lmv2Response; + } + + /** + * Get NTLM2SessionResponse + */ + public byte[] getNTLM2SessionResponse() { + if (ntlm2SessionResponse == null) { + ntlm2SessionResponse = ntlm2SessionResponse(getNTLMHash(), challenge, getClientChallenge()); + } + return ntlm2SessionResponse; + } + + /** + * Calculate and return LM2 session response + */ + public byte[] getLM2SessionResponse() { + if (lm2SessionResponse == null) { + final byte[] clntChallenge = getClientChallenge(); + lm2SessionResponse = new byte[24]; + System.arraycopy(clntChallenge, 0, lm2SessionResponse, 0, clntChallenge.length); + Arrays.fill(lm2SessionResponse, clntChallenge.length, lm2SessionResponse.length, (byte) 0x00); + } + return lm2SessionResponse; + } + + /** + * Get LMUserSessionKey + */ + public byte[] getLMUserSessionKey() { + if (lmUserSessionKey == null) { + lmUserSessionKey = new byte[16]; + System.arraycopy(getLMHash(), 0, lmUserSessionKey, 0, 8); + Arrays.fill(lmUserSessionKey, 8, 16, (byte) 0x00); + } + return lmUserSessionKey; + } + + /** + * Get NTLMUserSessionKey + */ + public byte[] getNTLMUserSessionKey() { + if (ntlmUserSessionKey == null) { + final MD4 md4 = new MD4(); + md4.update(getNTLMHash()); + ntlmUserSessionKey = md4.getOutput(); + } + return ntlmUserSessionKey; + } + + /** + * GetNTLMv2UserSessionKey + * + * @param targetInformation this parameter is the same object as the field targetInformation, + * but guaranteed to be not null. This is done to satisfy NullAway requirements + */ + public byte[] getNTLMv2UserSessionKey(byte[] targetInformation) { + if (ntlmv2UserSessionKey == null) { + final byte[] ntlmv2hash = getNTLMv2Hash(); + final byte[] truncatedResponse = new byte[16]; + System.arraycopy(getNTLMv2Response(targetInformation), 0, truncatedResponse, 0, 16); + ntlmv2UserSessionKey = hmacMD5(truncatedResponse, ntlmv2hash); + } + return ntlmv2UserSessionKey; + } + + /** + * Get NTLM2SessionResponseUserSessionKey + */ + public byte[] getNTLM2SessionResponseUserSessionKey() { + if (ntlm2SessionResponseUserSessionKey == null) { + final byte[] ntlm2SessionResponseNonce = getLM2SessionResponse(); + final byte[] sessionNonce = new byte[challenge.length + ntlm2SessionResponseNonce.length]; + System.arraycopy(challenge, 0, sessionNonce, 0, challenge.length); + System.arraycopy(ntlm2SessionResponseNonce, 0, sessionNonce, challenge.length, ntlm2SessionResponseNonce.length); + ntlm2SessionResponseUserSessionKey = hmacMD5(sessionNonce, getNTLMUserSessionKey()); + } + return ntlm2SessionResponseUserSessionKey; + } + + /** + * Get LAN Manager session key + */ + public byte[] getLanManagerSessionKey() { + if (lanManagerSessionKey == null) { + try { + final byte[] keyBytes = new byte[14]; + System.arraycopy(getLMHash(), 0, keyBytes, 0, 8); + Arrays.fill(keyBytes, 8, keyBytes.length, (byte) 0xbd); + final Key lowKey = createDESKey(keyBytes, 0); + final Key highKey = createDESKey(keyBytes, 7); + final byte[] truncatedResponse = new byte[8]; + System.arraycopy(getLMResponse(), 0, truncatedResponse, 0, truncatedResponse.length); + Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, lowKey); + final byte[] lowPart = des.doFinal(truncatedResponse); + des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, highKey); + final byte[] highPart = des.doFinal(truncatedResponse); + lanManagerSessionKey = new byte[16]; + System.arraycopy(lowPart, 0, lanManagerSessionKey, 0, lowPart.length); + System.arraycopy(highPart, 0, lanManagerSessionKey, lowPart.length, highPart.length); + } catch (final Exception e) { + throw new NtlmEngineException(e.getMessage(), e); + } + } + return lanManagerSessionKey; + } + } + + /** + * Calculates HMAC-MD5 + */ + private static byte[] hmacMD5(final byte[] value, final byte[] key) { + final HMACMD5 hmacMD5 = new HMACMD5(key); + hmacMD5.update(value); + return hmacMD5.getOutput(); + } + + /** + * Calculates RC4 + */ + private static byte[] RC4(final byte[] value, final byte[] key) { + try { + final Cipher rc4 = Cipher.getInstance("RC4"); + rc4.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "RC4")); + return rc4.doFinal(value); + } catch (final Exception e) { + throw new NtlmEngineException(e.getMessage(), e); + } + } + + /** + * Calculates the NTLM2 Session Response for the given challenge, using the + * specified password and client challenge. + * + * @return The NTLM2 Session Response. This is placed in the NTLM response + * field of the Type 3 message; the LM response field contains the + * client challenge, null-padded to 24 bytes. + */ + private static byte[] ntlm2SessionResponse(final byte[] ntlmHash, final byte[] challenge, final byte[] clientChallenge) { + try { + final MessageDigest md5 = MessageDigest.getInstance("MD5"); + md5.update(challenge); + md5.update(clientChallenge); + final byte[] digest = md5.digest(); + + final byte[] sessionHash = new byte[8]; + System.arraycopy(digest, 0, sessionHash, 0, 8); + return lmResponse(ntlmHash, sessionHash); + } catch (final Exception e) { + if (e instanceof NtlmEngineException) { + throw (NtlmEngineException) e; + } + throw new NtlmEngineException(e.getMessage(), e); + } + } + + /** + * Creates the LM Hash of the user's password. + * + * @param password The password. + * @return The LM Hash of the given password, used in the calculation of the + * LM Response. + */ + private static byte[] lmHash(final String password) { + try { + final byte[] oemPassword = password.toUpperCase(Locale.ROOT).getBytes(US_ASCII); + final int length = Math.min(oemPassword.length, 14); + final byte[] keyBytes = new byte[14]; + System.arraycopy(oemPassword, 0, keyBytes, 0, length); + final Key lowKey = createDESKey(keyBytes, 0); + final Key highKey = createDESKey(keyBytes, 7); + final Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, lowKey); + final byte[] lowHash = des.doFinal(MAGIC_CONSTANT); + des.init(Cipher.ENCRYPT_MODE, highKey); + final byte[] highHash = des.doFinal(MAGIC_CONSTANT); + final byte[] lmHash = new byte[16]; + System.arraycopy(lowHash, 0, lmHash, 0, 8); + System.arraycopy(highHash, 0, lmHash, 8, 8); + return lmHash; + } catch (final Exception e) { + throw new NtlmEngineException(e.getMessage(), e); + } + } + + /** + * Creates the NTLM Hash of the user's password. + * + * @param password The password. + * @return The NTLM Hash of the given password, used in the calculation of + * the NTLM Response and the NTLMv2 and LMv2 Hashes. + */ + private static byte[] ntlmHash(final String password) { + final byte[] unicodePassword = password.getBytes(UNICODE_LITTLE_UNMARKED); + final MD4 md4 = new MD4(); + md4.update(unicodePassword); + return md4.getOutput(); + } + + /** + * Creates the LMv2 Hash of the user's password. + * + * @return The LMv2 Hash, used in the calculation of the NTLMv2 and LMv2 + * Responses. + */ + private static byte[] lmv2Hash(final String domain, final String user, final byte[] ntlmHash) { + final HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); + // Upper case username, upper case domain! + hmacMD5.update(user.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED)); + if (domain != null) { + hmacMD5.update(domain.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED)); + } + return hmacMD5.getOutput(); + } + + /** + * Creates the NTLMv2 Hash of the user's password. + * + * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 and LMv2 + * Responses. + */ + private static byte[] ntlmv2Hash(final String domain, final String user, final byte[] ntlmHash) { + final HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); + // Upper case username, mixed case target!! + hmacMD5.update(user.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED)); + if (domain != null) { + hmacMD5.update(domain.getBytes(UNICODE_LITTLE_UNMARKED)); + } + return hmacMD5.getOutput(); + } + + /** + * Creates the LM Response from the given hash and Type 2 challenge. + * + * @param hash The LM or NTLM Hash. + * @param challenge The server challenge from the Type 2 message. + * @return The response (either LM or NTLM, depending on the provided hash). + */ + private static byte[] lmResponse(final byte[] hash, final byte[] challenge) { + try { + final byte[] keyBytes = new byte[21]; + System.arraycopy(hash, 0, keyBytes, 0, 16); + final Key lowKey = createDESKey(keyBytes, 0); + final Key middleKey = createDESKey(keyBytes, 7); + final Key highKey = createDESKey(keyBytes, 14); + final Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, lowKey); + final byte[] lowResponse = des.doFinal(challenge); + des.init(Cipher.ENCRYPT_MODE, middleKey); + final byte[] middleResponse = des.doFinal(challenge); + des.init(Cipher.ENCRYPT_MODE, highKey); + final byte[] highResponse = des.doFinal(challenge); + final byte[] lmResponse = new byte[24]; + System.arraycopy(lowResponse, 0, lmResponse, 0, 8); + System.arraycopy(middleResponse, 0, lmResponse, 8, 8); + System.arraycopy(highResponse, 0, lmResponse, 16, 8); + return lmResponse; + } catch (final Exception e) { + throw new NtlmEngineException(e.getMessage(), e); + } + } + + /** + * Creates the LMv2 Response from the given hash, client data, and Type 2 + * challenge. + * + * @param hash The NTLMv2 Hash. + * @param clientData The client data (blob or client challenge). + * @param challenge The server challenge from the Type 2 message. + * @return The response (either NTLMv2 or LMv2, depending on the client + * data). + */ + private static byte[] lmv2Response(final byte[] hash, final byte[] challenge, final byte[] clientData) { + final HMACMD5 hmacMD5 = new HMACMD5(hash); + hmacMD5.update(challenge); + hmacMD5.update(clientData); + final byte[] mac = hmacMD5.getOutput(); + final byte[] lmv2Response = new byte[mac.length + clientData.length]; + System.arraycopy(mac, 0, lmv2Response, 0, mac.length); + System.arraycopy(clientData, 0, lmv2Response, mac.length, clientData.length); + return lmv2Response; + } + + /** + * Creates the NTLMv2 blob from the given target information block and + * client challenge. + * + * @param targetInformation The target information block from the Type 2 message. + * @param clientChallenge The random 8-byte client challenge. + * @return The blob, used in the calculation of the NTLMv2 Response. + */ + private static byte[] createBlob(final byte[] clientChallenge, final byte[] targetInformation, final byte[] timestamp) { + final byte[] blobSignature = {(byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00}; + final byte[] reserved = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}; + final byte[] unknown1 = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}; + final byte[] unknown2 = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}; + final byte[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + 8 + unknown1.length + + targetInformation.length + unknown2.length]; + int offset = 0; + System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length); + offset += blobSignature.length; + System.arraycopy(reserved, 0, blob, offset, reserved.length); + offset += reserved.length; + System.arraycopy(timestamp, 0, blob, offset, timestamp.length); + offset += timestamp.length; + System.arraycopy(clientChallenge, 0, blob, offset, 8); + offset += 8; + System.arraycopy(unknown1, 0, blob, offset, unknown1.length); + offset += unknown1.length; + System.arraycopy(targetInformation, 0, blob, offset, targetInformation.length); + offset += targetInformation.length; + System.arraycopy(unknown2, 0, blob, offset, unknown2.length); + return blob; + } + + /** + * Creates a DES encryption key from the given key material. + * + * @param bytes A byte array containing the DES key material. + * @param offset The offset in the given byte array at which the 7-byte key + * material starts. + * @return A DES encryption key created from the key material starting at + * the specified offset in the given byte array. + */ + private static Key createDESKey(final byte[] bytes, final int offset) { + final byte[] keyBytes = new byte[7]; + System.arraycopy(bytes, offset, keyBytes, 0, 7); + final byte[] material = new byte[8]; + material[0] = keyBytes[0]; + material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1); + material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2); + material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3); + material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4); + material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5); + material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6); + material[7] = (byte) (keyBytes[6] << 1); + oddParity(material); + return new SecretKeySpec(material, "DES"); + } + + /** + * Applies odd parity to the given byte array. + * + * @param bytes The data whose parity bits are to be adjusted for odd parity. + */ + private static void oddParity(final byte[] bytes) { + for (int i = 0; i < bytes.length; i++) { + final byte b = bytes[i]; + final boolean needsParity = ((b >>> 7 ^ b >>> 6 ^ b >>> 5 ^ b >>> 4 ^ b >>> 3 ^ b >>> 2 ^ b >>> 1) & 0x01) == 0; + if (needsParity) { + bytes[i] |= 0x01; + } else { + bytes[i] &= (byte) 0xfe; + } + } + } + + /** + * NTLM message generation, base class + */ + private static class NTLMMessage { + private static final byte[] EMPTY_BYTE_ARRAY = {}; + /** + * The current response + */ + private byte[] messageContents = EMPTY_BYTE_ARRAY; + + /** + * The current output position + */ + private int currentOutputPosition; + + /** + * Constructor to use when message contents are not yet known + */ + NTLMMessage() { + } + + /** + * Constructor to use when message contents are known + */ + NTLMMessage(final String messageBody, final int expectedType) { + messageContents = Base64.getDecoder().decode(messageBody); + // Look for NTLM message + if (messageContents.length < SIGNATURE.length) { + throw new NtlmEngineException("NTLM message decoding error - packet too short"); + } + int i = 0; + while (i < SIGNATURE.length) { + if (messageContents[i] != SIGNATURE[i]) { + throw new NtlmEngineException("NTLM message expected - instead got unrecognized bytes"); + } + i++; + } + + // Check to be sure there's a type 2 message indicator next + final int type = readULong(SIGNATURE.length); + if (type != expectedType) { + throw new NtlmEngineException("NTLM type " + expectedType + " message expected - instead got type " + + type); + } + + currentOutputPosition = messageContents.length; + } + + /** + * Get the length of the signature and flags, so calculations can adjust + * offsets accordingly. + */ + protected int getPreambleLength() { + return SIGNATURE.length + 4; + } + + /** + * Get the message length + */ + protected final int getMessageLength() { + return currentOutputPosition; + } + + /** + * Read a byte from a position within the message buffer + */ + protected byte readByte(final int position) { + if (messageContents.length < position + 1) { + throw new NtlmEngineException("NTLM: Message too short"); + } + return messageContents[position]; + } + + /** + * Read a bunch of bytes from a position in the message buffer + */ + protected final void readBytes(final byte[] buffer, final int position) { + if (messageContents.length < position + buffer.length) { + throw new NtlmEngineException("NTLM: Message too short"); + } + System.arraycopy(messageContents, position, buffer, 0, buffer.length); + } + + /** + * Read an ushort from a position within the message buffer + */ + protected int readUShort(final int position) { + return NtlmEngine.readUShort(messageContents, position); + } + + /** + * Read an ulong from a position within the message buffer + */ + protected final int readULong(final int position) { + return NtlmEngine.readULong(messageContents, position); + } + + /** + * Read a security buffer from a position within the message buffer + */ + protected final byte[] readSecurityBuffer(final int position) { + return NtlmEngine.readSecurityBuffer(messageContents, position); + } + + /** + * Prepares the object to create a response of the given length. + * + * @param maxlength the maximum length of the response to prepare, not + * including the type and the signature (which this method + * adds). + */ + protected void prepareResponse(final int maxlength, final int messageType) { + messageContents = new byte[maxlength]; + currentOutputPosition = 0; + addBytes(SIGNATURE); + addULong(messageType); + } + + /** + * Adds the given byte to the response. + * + * @param b the byte to add. + */ + protected void addByte(final byte b) { + messageContents[currentOutputPosition] = b; + currentOutputPosition++; + } + + /** + * Adds the given bytes to the response. + * + * @param bytes the bytes to add. + */ + protected void addBytes(final byte @Nullable [] bytes) { + if (bytes == null) { + return; + } + for (final byte b : bytes) { + messageContents[currentOutputPosition] = b; + currentOutputPosition++; + } + } + + /** + * Adds a USHORT to the response + */ + protected void addUShort(final int value) { + addByte((byte) (value & 0xff)); + addByte((byte) (value >> 8 & 0xff)); + } + + /** + * Adds a ULong to the response + */ + protected void addULong(final int value) { + addByte((byte) (value & 0xff)); + addByte((byte) (value >> 8 & 0xff)); + addByte((byte) (value >> 16 & 0xff)); + addByte((byte) (value >> 24 & 0xff)); + } + + /** + * Returns the response that has been generated after shrinking the + * array if required and base64 encodes the response. + * + * @return The response as above. + */ + String getResponse() { + final byte[] resp; + if (messageContents.length > currentOutputPosition) { + final byte[] tmp = new byte[currentOutputPosition]; + System.arraycopy(messageContents, 0, tmp, 0, currentOutputPosition); + resp = tmp; + } else { + resp = messageContents; + } + return Base64.getEncoder().encodeToString(resp); + } + + } + + /** + * Type 1 message assembly class + */ + private static class Type1Message extends NTLMMessage { + + /** + * Getting the response involves building the message before returning + * it + */ + @Override + String getResponse() { + // Now, build the message. Calculate its length first, including + // signature or type. + final int finalLength = 32 + 8; + + // Set up the response. This will initialize the signature, message + // type, and flags. + prepareResponse(finalLength, 1); + + // Flags. These are the complete set of flags we support. + addULong( + //FLAG_WORKSTATION_PRESENT | + //FLAG_DOMAIN_PRESENT | + + // Required flags + //FLAG_REQUEST_LAN_MANAGER_KEY | + FLAG_REQUEST_NTLMv1 | FLAG_REQUEST_NTLM2_SESSION | + + // Protocol version request + FLAG_REQUEST_VERSION | + + // Recommended privacy settings + FLAG_REQUEST_ALWAYS_SIGN | + //FLAG_REQUEST_SEAL | + //FLAG_REQUEST_SIGN | + + // These must be set according to documentation, based on use of SEAL above + FLAG_REQUEST_128BIT_KEY_EXCH | FLAG_REQUEST_56BIT_ENCRYPTION | + //FLAG_REQUEST_EXPLICIT_KEY_EXCH | + + FLAG_REQUEST_UNICODE_ENCODING); + + // Domain length (two times). + addUShort(0); + addUShort(0); + + // Domain offset. + addULong(finalLength); + + // Host length (two times). + addUShort(0); + addUShort(0); + + // Host offset (always 32 + 8). + addULong(finalLength); + + // Version + addUShort(0x0105); + // Build + addULong(2600); + // NTLM revision + addUShort(0x0f00); + + return super.getResponse(); + } + } + + /** + * Type 2 message class + */ + static class Type2Message extends NTLMMessage { + protected byte[] challenge; + protected @Nullable String target; + protected byte @Nullable [] targetInfo; + protected int flags; + + Type2Message(final String message) { + super(message, 2); + + // Type 2 message is laid out as follows: + // First 8 bytes: NTLMSSP[0] + // Next 4 bytes: Ulong, value 2 + // Next 8 bytes, starting at offset 12: target field (2 ushort lengths, 1 ulong offset) + // Next 4 bytes, starting at offset 20: Flags, e.g. 0x22890235 + // Next 8 bytes, starting at offset 24: Challenge + // Next 8 bytes, starting at offset 32: ??? (8 bytes of zeros) + // Next 8 bytes, starting at offset 40: targetinfo field (2 ushort lengths, 1 ulong offset) + // 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 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. + challenge = new byte[8]; + readBytes(challenge, 24); + + flags = readULong(20); + + if ((flags & FLAG_REQUEST_UNICODE_ENCODING) == 0) { + throw new NtlmEngineException("NTLM type 2 message indicates no support for Unicode. Flags are: " + flags); + } + + // Do the target! + target = null; + // The TARGET_DESIRED flag is said to not have understood semantics + // in Type2 messages, so use the length of the packet to decide + // how to proceed instead + if (getMessageLength() >= 12 + 8) { + final byte[] bytes = readSecurityBuffer(12); + if (bytes.length != 0) { + try { + target = new String(bytes, "UnicodeLittleUnmarked"); + } catch (final UnsupportedEncodingException e) { + throw new NtlmEngineException(e.getMessage(), e); + } + } + } + + // Do the target info! + targetInfo = null; + // TARGET_DESIRED flag cannot be relied on, so use packet length + if (getMessageLength() >= 40 + 8) { + final byte[] bytes = readSecurityBuffer(40); + if (bytes.length != 0) { + targetInfo = bytes; + } + } + } + + /** + * Retrieve the challenge + */ + byte[] getChallenge() { + return challenge; + } + + /** + * Retrieve the target + */ + @Nullable + String getTarget() { + return target; + } + + /** + * Retrieve the target info + */ + byte @Nullable [] getTargetInfo() { + return targetInfo; + } + + /** + * Retrieve the response flags + */ + int getFlags() { + return flags; + } + + } + + /** + * Type 3 message assembly class + */ + static class Type3Message extends NTLMMessage { + // Response flags from the type2 message + protected int type2Flags; + + protected byte @Nullable [] domainBytes; + protected byte @Nullable [] hostBytes; + protected byte[] userBytes; + + protected byte[] lmResp; + protected byte[] ntResp; + protected byte @Nullable [] sessionKey; + + /** + * Constructor. Pass the arguments we will need + */ + Type3Message(final String domain, final String host, final String user, final String password, final byte[] nonce, + final int type2Flags, final @Nullable String target, final byte @Nullable [] targetInformation) { + // Save the flags + this.type2Flags = type2Flags; + + // Strip off domain name from the host! + final String unqualifiedHost = convertHost(host); + // Use only the base domain name! + final String unqualifiedDomain = convertDomain(domain); + + // Create a cipher generator class. Use domain BEFORE it gets modified! + final CipherGen gen = new CipherGen(unqualifiedDomain, user, password, nonce, target, targetInformation); + + // Use the new code to calculate the responses, including v2 if that + // seems warranted. + byte[] userSessionKey; + try { + // This conditional may not work on Windows Server 2008 R2 and above, where it has not yet + // been tested + if ((type2Flags & FLAG_TARGETINFO_PRESENT) != 0 && targetInformation != null && target != null) { + // NTLMv2 + ntResp = gen.getNTLMv2Response(targetInformation); + lmResp = gen.getLMv2Response(); + if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { + userSessionKey = gen.getLanManagerSessionKey(); + } else { + userSessionKey = gen.getNTLMv2UserSessionKey(targetInformation); + } + } else { + // NTLMv1 + if ((type2Flags & FLAG_REQUEST_NTLM2_SESSION) != 0) { + // NTLM2 session stuff is requested + ntResp = gen.getNTLM2SessionResponse(); + lmResp = gen.getLM2SessionResponse(); + if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { + userSessionKey = gen.getLanManagerSessionKey(); + } else { + userSessionKey = gen.getNTLM2SessionResponseUserSessionKey(); + } + } else { + ntResp = gen.getNTLMResponse(); + lmResp = gen.getLMResponse(); + if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { + userSessionKey = gen.getLanManagerSessionKey(); + } else { + userSessionKey = gen.getNTLMUserSessionKey(); + } + } + } + } catch (final NtlmEngineException e) { + // This likely means we couldn't find the MD4 hash algorithm - + // fail back to just using LM + ntResp = new byte[0]; + lmResp = gen.getLMResponse(); + if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { + userSessionKey = gen.getLanManagerSessionKey(); + } else { + userSessionKey = gen.getLMUserSessionKey(); + } + } + + if ((type2Flags & FLAG_REQUEST_SIGN) != 0) { + if ((type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) != 0) { + sessionKey = RC4(gen.getSecondaryKey(), userSessionKey); + } else { + sessionKey = userSessionKey; + } + } else { + sessionKey = null; + } + hostBytes = unqualifiedHost != null ? unqualifiedHost.getBytes(UNICODE_LITTLE_UNMARKED) : null; + domainBytes = unqualifiedDomain != null ? unqualifiedDomain.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED) : null; + userBytes = user.getBytes(UNICODE_LITTLE_UNMARKED); + } + + /** + * Assemble the response + */ + @Override + String getResponse() { + final int ntRespLen = ntResp.length; + final int lmRespLen = lmResp.length; + + final int domainLen = domainBytes != null ? domainBytes.length : 0; + final int hostLen = hostBytes != null ? hostBytes.length : 0; + final int userLen = userBytes.length; + final int sessionKeyLen; + if (sessionKey != null) { + sessionKeyLen = sessionKey.length; + } else { + sessionKeyLen = 0; + } + + // Calculate the layout within the packet + final int lmRespOffset = 72; // allocate space for the version + final int ntRespOffset = lmRespOffset + lmRespLen; + final int domainOffset = ntRespOffset + ntRespLen; + final int userOffset = domainOffset + domainLen; + final int hostOffset = userOffset + userLen; + final int sessionKeyOffset = hostOffset + hostLen; + final int finalLength = sessionKeyOffset + sessionKeyLen; + + // Start the response. Length includes signature and type + prepareResponse(finalLength, 3); + + // LM Resp Length (twice) + addUShort(lmRespLen); + addUShort(lmRespLen); + + // LM Resp Offset + addULong(lmRespOffset); + + // NT Resp Length (twice) + addUShort(ntRespLen); + addUShort(ntRespLen); + + // NT Resp Offset + addULong(ntRespOffset); + + // Domain length (twice) + addUShort(domainLen); + addUShort(domainLen); + + // Domain offset. + addULong(domainOffset); + + // User Length (twice) + addUShort(userLen); + addUShort(userLen); + + // User offset + addULong(userOffset); + + // Host length (twice) + addUShort(hostLen); + addUShort(hostLen); + + // Host offset + addULong(hostOffset); + + // Session key length (twice) + addUShort(sessionKeyLen); + addUShort(sessionKeyLen); + + // Session key offset + addULong(sessionKeyOffset); + + // Flags. + addULong( + //FLAG_WORKSTATION_PRESENT | + //FLAG_DOMAIN_PRESENT | + + // Required flags + type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY + | type2Flags & FLAG_REQUEST_NTLMv1 + | type2Flags & FLAG_REQUEST_NTLM2_SESSION + | + + // Protocol version request + FLAG_REQUEST_VERSION + | + + // Recommended privacy settings + type2Flags & FLAG_REQUEST_ALWAYS_SIGN | type2Flags & FLAG_REQUEST_SEAL + | type2Flags & FLAG_REQUEST_SIGN + | + + // These must be set according to documentation, based on use of SEAL above + type2Flags & FLAG_REQUEST_128BIT_KEY_EXCH | type2Flags & FLAG_REQUEST_56BIT_ENCRYPTION + | type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH | + + type2Flags & FLAG_TARGETINFO_PRESENT | type2Flags & FLAG_REQUEST_UNICODE_ENCODING + | type2Flags & FLAG_REQUEST_TARGET); + + // Version + addUShort(0x0105); + // Build + addULong(2600); + // NTLM revision + addUShort(0x0f00); + + // Add the actual data + addBytes(lmResp); + addBytes(ntResp); + addBytes(domainBytes); + addBytes(userBytes); + addBytes(hostBytes); + if (sessionKey != null) { + addBytes(sessionKey); + } + + return super.getResponse(); + } + } + + static void writeULong(final byte[] buffer, final int value, final int offset) { + buffer[offset] = (byte) (value & 0xff); + buffer[offset + 1] = (byte) (value >> 8 & 0xff); + buffer[offset + 2] = (byte) (value >> 16 & 0xff); + buffer[offset + 3] = (byte) (value >> 24 & 0xff); + } + + static int F(final int x, final int y, final int z) { + return x & y | ~x & z; + } + + static int G(final int x, final int y, final int z) { + return x & y | x & z | y & z; + } + + static int H(final int x, final int y, final int z) { + return x ^ y ^ z; + } + + static int rotintlft(final int val, final int numbits) { + return val << numbits | val >>> 32 - numbits; + } + + /** + * Cryptography support - MD4. The following class was based loosely on the + * RFC and on code found at .... + * Code correctness was verified by looking at MD4.java from the jcifs + * library (...). It was massaged extensively to the + * final form found here by Karl Wright (kwright@metacarta.com). + */ + static class MD4 { + protected int A = 0x67452301; + protected int B = 0xefcdab89; + protected int C = 0x98badcfe; + protected int D = 0x10325476; + protected long count; + protected byte[] dataBuffer = new byte[64]; + + void update(final byte[] input) { + // We always deal with 512 bits at a time. Correspondingly, there is + // a buffer 64 bytes long that we write data into until it gets + // full. + int curBufferPos = (int) (count & 63L); + int inputIndex = 0; + while (input.length - inputIndex + curBufferPos >= dataBuffer.length) { + // We have enough data to do the next step. Do a partial copy + // and a transform, updating inputIndex and curBufferPos + // accordingly + final int transferAmt = dataBuffer.length - curBufferPos; + System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); + count += transferAmt; + curBufferPos = 0; + inputIndex += transferAmt; + processBuffer(); + } + + // If there's anything left, copy it into the buffer and leave it. + // We know there's not enough left to process. + if (inputIndex < input.length) { + final int transferAmt = input.length - inputIndex; + System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); + count += transferAmt; + } + } + + byte[] getOutput() { + // Feed pad/length data into engine. This must round out the input + // to a multiple of 512 bits. + final int bufferIndex = (int) (count & 63L); + final int padLen = bufferIndex < 56 ? 56 - bufferIndex : 120 - bufferIndex; + final byte[] postBytes = new byte[padLen + 8]; + // Leading 0x80, specified amount of zero padding, then length in + // bits. + postBytes[0] = (byte) 0x80; + // Fill out the last 8 bytes with the length + for (int i = 0; i < 8; i++) { + postBytes[padLen + i] = (byte) (count * 8 >>> 8 * i); + } + + // Update the engine + update(postBytes); + + // Calculate final result + final byte[] result = new byte[16]; + writeULong(result, A, 0); + writeULong(result, B, 4); + writeULong(result, C, 8); + writeULong(result, D, 12); + return result; + } + + protected void processBuffer() { + // Convert current buffer to 16 ulongs + final int[] d = new int[16]; + + for (int i = 0; i < 16; i++) { + d[i] = (dataBuffer[i * 4] & 0xff) + ((dataBuffer[i * 4 + 1] & 0xff) << 8) + ((dataBuffer[i * 4 + 2] & 0xff) << 16) + + ((dataBuffer[i * 4 + 3] & 0xff) << 24); + } + + // Do a round of processing + final int AA = A; + final int BB = B; + final int CC = C; + final int DD = D; + round1(d); + round2(d); + round3(d); + A += AA; + B += BB; + C += CC; + D += DD; + + } + + protected void round1(final int[] d) { + A = rotintlft(A + F(B, C, D) + d[0], 3); + D = rotintlft(D + F(A, B, C) + d[1], 7); + C = rotintlft(C + F(D, A, B) + d[2], 11); + B = rotintlft(B + F(C, D, A) + d[3], 19); + + A = rotintlft(A + F(B, C, D) + d[4], 3); + D = rotintlft(D + F(A, B, C) + d[5], 7); + C = rotintlft(C + F(D, A, B) + d[6], 11); + B = rotintlft(B + F(C, D, A) + d[7], 19); + + A = rotintlft(A + F(B, C, D) + d[8], 3); + D = rotintlft(D + F(A, B, C) + d[9], 7); + C = rotintlft(C + F(D, A, B) + d[10], 11); + B = rotintlft(B + F(C, D, A) + d[11], 19); + + A = rotintlft(A + F(B, C, D) + d[12], 3); + D = rotintlft(D + F(A, B, C) + d[13], 7); + C = rotintlft(C + F(D, A, B) + d[14], 11); + B = rotintlft(B + F(C, D, A) + d[15], 19); + } + + protected void round2(final int[] d) { + A = rotintlft(A + G(B, C, D) + d[0] + 0x5a827999, 3); + D = rotintlft(D + G(A, B, C) + d[4] + 0x5a827999, 5); + C = rotintlft(C + G(D, A, B) + d[8] + 0x5a827999, 9); + B = rotintlft(B + G(C, D, A) + d[12] + 0x5a827999, 13); + + A = rotintlft(A + G(B, C, D) + d[1] + 0x5a827999, 3); + D = rotintlft(D + G(A, B, C) + d[5] + 0x5a827999, 5); + C = rotintlft(C + G(D, A, B) + d[9] + 0x5a827999, 9); + B = rotintlft(B + G(C, D, A) + d[13] + 0x5a827999, 13); + + A = rotintlft(A + G(B, C, D) + d[2] + 0x5a827999, 3); + D = rotintlft(D + G(A, B, C) + d[6] + 0x5a827999, 5); + C = rotintlft(C + G(D, A, B) + d[10] + 0x5a827999, 9); + B = rotintlft(B + G(C, D, A) + d[14] + 0x5a827999, 13); + + A = rotintlft(A + G(B, C, D) + d[3] + 0x5a827999, 3); + D = rotintlft(D + G(A, B, C) + d[7] + 0x5a827999, 5); + C = rotintlft(C + G(D, A, B) + d[11] + 0x5a827999, 9); + B = rotintlft(B + G(C, D, A) + d[15] + 0x5a827999, 13); + + } + + protected void round3(final int[] d) { + A = rotintlft(A + H(B, C, D) + d[0] + 0x6ed9eba1, 3); + D = rotintlft(D + H(A, B, C) + d[8] + 0x6ed9eba1, 9); + C = rotintlft(C + H(D, A, B) + d[4] + 0x6ed9eba1, 11); + B = rotintlft(B + H(C, D, A) + d[12] + 0x6ed9eba1, 15); + + A = rotintlft(A + H(B, C, D) + d[2] + 0x6ed9eba1, 3); + D = rotintlft(D + H(A, B, C) + d[10] + 0x6ed9eba1, 9); + C = rotintlft(C + H(D, A, B) + d[6] + 0x6ed9eba1, 11); + B = rotintlft(B + H(C, D, A) + d[14] + 0x6ed9eba1, 15); + + A = rotintlft(A + H(B, C, D) + d[1] + 0x6ed9eba1, 3); + D = rotintlft(D + H(A, B, C) + d[9] + 0x6ed9eba1, 9); + C = rotintlft(C + H(D, A, B) + d[5] + 0x6ed9eba1, 11); + B = rotintlft(B + H(C, D, A) + d[13] + 0x6ed9eba1, 15); + + A = rotintlft(A + H(B, C, D) + d[3] + 0x6ed9eba1, 3); + D = rotintlft(D + H(A, B, C) + d[11] + 0x6ed9eba1, 9); + C = rotintlft(C + H(D, A, B) + d[7] + 0x6ed9eba1, 11); + B = rotintlft(B + H(C, D, A) + d[15] + 0x6ed9eba1, 15); + } + } + + /** + * Cryptography support - HMACMD5 - algorithmically based on various web + * resources by Karl Wright + */ + private static class HMACMD5 { + protected byte[] ipad; + protected byte[] opad; + protected MessageDigest md5; + + HMACMD5(final byte[] input) { + byte[] key = input; + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (final Exception ex) { + // Umm, the algorithm doesn't exist - throw an + // NTLMEngineException! + throw new NtlmEngineException("Error getting md5 message digest implementation: " + ex.getMessage(), ex); + } + + // Initialize the pad buffers with the key + ipad = new byte[64]; + opad = new byte[64]; + + int keyLength = key.length; + if (keyLength > 64) { + // Use MD5 of the key instead, as described in RFC 2104 + md5.update(key); + key = md5.digest(); + keyLength = key.length; + } + int i = 0; + while (i < keyLength) { + ipad[i] = (byte) (key[i] ^ (byte) 0x36); + opad[i] = (byte) (key[i] ^ (byte) 0x5c); + i++; + } + while (i < 64) { + ipad[i] = 0x36; + opad[i] = 0x5c; + i++; + } + + // Very important: update the digest with the ipad buffer + md5.reset(); + md5.update(ipad); + + } + + /** + * Grab the current digest. This is the "answer". + */ + byte[] getOutput() { + final byte[] digest = md5.digest(); + md5.update(opad); + return md5.digest(digest); + } + + /** + * Update by adding a complete array + */ + void update(final byte[] input) { + md5.update(input); + } + } + + /** + * Creates the first message (type 1 message) in the NTLM authentication + * sequence. This message includes the username, domain and host for the + * authentication session. + * + * @return String the message to add to the HTTP request header. + */ + public String generateType1Msg() { + return TYPE_1_MESSAGE; + } + + public static String generateType3Msg(final String username, final String password, final String domain, final String workstation, + final String challenge) { + final Type2Message t2m = new Type2Message(challenge); + return getType3Message(username, password, workstation, domain, t2m.getChallenge(), t2m.getFlags(), t2m.getTarget(), + t2m.getTargetInfo()); + } + +} \ No newline at end of file diff --git a/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngineException.java b/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngineException.java new file mode 100644 index 0000000000..dd7827cbf7 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngineException.java @@ -0,0 +1,57 @@ +/* + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.asynchttpclient.ntlm; + + +import org.jetbrains.annotations.Nullable; + +/** + * Signals NTLM protocol failure. + */ +class NtlmEngineException extends RuntimeException { + + private static final long serialVersionUID = 6027981323731768824L; + + /** + * Creates a new NTLMEngineException with the specified message. + * + * @param message the exception detail message + */ + NtlmEngineException(String message) { + super(message); + } + + /** + * Creates a new NTLMEngineException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the Throwable that caused this exception, or null + * if the cause is unavailable, unknown, or not a Throwable + */ + NtlmEngineException(@Nullable String message, Throwable cause) { + super(message, cause); + } +} diff --git a/client/src/main/java/org/asynchttpclient/proxy/ProxyServer.java b/client/src/main/java/org/asynchttpclient/proxy/ProxyServer.java new file mode 100644 index 0000000000..9cb33362c5 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/proxy/ProxyServer.java @@ -0,0 +1,189 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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.proxy; + +import io.netty.handler.codec.http.HttpHeaders; +import org.asynchttpclient.Realm; +import org.asynchttpclient.Request; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +import static java.util.Objects.requireNonNull; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; + +/** + * Represents a proxy server. + */ +public class ProxyServer { + + private final String host; + private final int port; + private final int securedPort; + private final @Nullable Realm realm; + private final List nonProxyHosts; + private final ProxyType proxyType; + private final @Nullable Function customHeaders; + + public ProxyServer(String host, int port, int securedPort, @Nullable Realm realm, List nonProxyHosts, ProxyType proxyType, + @Nullable Function customHeaders) { + this.host = host; + this.port = port; + this.securedPort = securedPort; + this.realm = realm; + this.nonProxyHosts = nonProxyHosts; + this.proxyType = proxyType; + this.customHeaders = customHeaders; + } + + public ProxyServer(String host, int port, int securedPort, Realm realm, List nonProxyHosts, ProxyType proxyType) { + this(host, port, securedPort, realm, nonProxyHosts, proxyType, null); + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public int getSecuredPort() { + return securedPort; + } + + public List getNonProxyHosts() { + return nonProxyHosts; + } + + public @Nullable Realm getRealm() { + return realm; + } + + public ProxyType getProxyType() { + return proxyType; + } + + public @Nullable Function getCustomHeaders() { + return customHeaders; + } + + /** + * Checks whether proxy should be used according to nonProxyHosts settings of + * it, or we want to go directly to target host. If {@code null} proxy is + * passed in, this method returns true -- since there is NO proxy, we should + * avoid to use it. Simple hostname pattern matching using "*" are supported, + * but only as prefixes. + * + * @param hostname the hostname + * @return true if we have to ignore proxy use (obeying non-proxy hosts + * settings), false otherwise. + * @see Networking + * Properties + */ + public boolean isIgnoredForHost(String hostname) { + requireNonNull(hostname, "hostname"); + if (isNonEmpty(nonProxyHosts)) { + for (String nonProxyHost : nonProxyHosts) { + if (matchNonProxyHost(hostname, nonProxyHost)) { + return true; + } + } + } + + return false; + } + + private static boolean matchNonProxyHost(String targetHost, String nonProxyHost) { + + if (nonProxyHost.length() > 1) { + if (nonProxyHost.charAt(0) == '*') { + return targetHost.regionMatches(true, targetHost.length() - nonProxyHost.length() + 1, nonProxyHost, 1, + nonProxyHost.length() - 1); + } else if (nonProxyHost.charAt(nonProxyHost.length() - 1) == '*') { + return targetHost.regionMatches(true, 0, nonProxyHost, 0, nonProxyHost.length() - 1); + } + } + + return nonProxyHost.equalsIgnoreCase(targetHost); + } + + public static class Builder { + + private final String host; + private final int port; + private int securedPort; + private @Nullable Realm realm; + private @Nullable List nonProxyHosts; + private @Nullable ProxyType proxyType; + private @Nullable Function customHeaders; + + public Builder(String host, int port) { + this.host = host; + this.port = port; + securedPort = port; + } + + public Builder setSecuredPort(int securedPort) { + this.securedPort = securedPort; + return this; + } + + public Builder setRealm(@Nullable Realm realm) { + this.realm = realm; + return this; + } + + public Builder setRealm(Realm.Builder realm) { + this.realm = realm.build(); + return this; + } + + public Builder setNonProxyHost(String nonProxyHost) { + if (nonProxyHosts == null) { + nonProxyHosts = new ArrayList<>(1); + } + nonProxyHosts.add(nonProxyHost); + return this; + } + + public Builder setNonProxyHosts(List nonProxyHosts) { + this.nonProxyHosts = nonProxyHosts; + return this; + } + + public Builder setProxyType(ProxyType proxyType) { + this.proxyType = proxyType; + return this; + } + + public Builder setCustomHeaders(Function customHeaders) { + this.customHeaders = customHeaders; + return this; + } + + public ProxyServer build() { + List nonProxyHosts = this.nonProxyHosts != null ? Collections.unmodifiableList(this.nonProxyHosts) : Collections.emptyList(); + ProxyType proxyType = this.proxyType != null ? this.proxyType : ProxyType.HTTP; + return new ProxyServer(host, port, securedPort, realm, nonProxyHosts, proxyType, customHeaders); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java b/client/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java new file mode 100644 index 0000000000..048f2e78ed --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java @@ -0,0 +1,40 @@ +/* + * 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.proxy; + +import org.asynchttpclient.uri.Uri; +import org.jetbrains.annotations.Nullable; + +/** + * Selector for a proxy server + */ +@FunctionalInterface +public interface ProxyServerSelector { + + /** + * A selector that always selects no proxy. + */ + ProxyServerSelector NO_PROXY_SELECTOR = uri -> null; + + /** + * Select a proxy server to use for the given URI. + * + * @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); +} diff --git a/client/src/main/java/org/asynchttpclient/proxy/ProxyType.java b/client/src/main/java/org/asynchttpclient/proxy/ProxyType.java new file mode 100644 index 0000000000..d1f74e70d7 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/proxy/ProxyType.java @@ -0,0 +1,34 @@ +/* + * 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.proxy; + +public enum ProxyType { + HTTP(true), SOCKS_V4(false), SOCKS_V5(false); + + private final boolean http; + + ProxyType(boolean http) { + this.http = http; + } + + public boolean isHttp() { + return http; + } + + public boolean isSocks() { + return !isHttp(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/Body.java b/client/src/main/java/org/asynchttpclient/request/body/Body.java new file mode 100644 index 0000000000..6e38107fcd --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/Body.java @@ -0,0 +1,58 @@ +/* + * 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.netty.buffer.ByteBuf; + +import java.io.Closeable; +import java.io.IOException; + +/** + * A request body. + */ +public interface Body extends Closeable { + + /** + * Gets the length of the body. + * + * @return The length of the body in bytes, or negative if unknown. + */ + long getContentLength(); + + /** + * Reads the next chunk of bytes from the body. + * + * @param target The buffer to store the chunk in, must not be {@code null}. + * @return The state. + * @throws IOException If the chunk could not be read. + */ + BodyState transferTo(ByteBuf target) throws IOException; + + enum BodyState { + + /** + * There's something to read + */ + CONTINUE, + + /** + * There's nothing to read and input has to suspend + */ + SUSPEND, + + /** + * There's nothing to read and input has to stop + */ + STOP + } +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/RandomAccessBody.java b/client/src/main/java/org/asynchttpclient/request/body/RandomAccessBody.java new file mode 100644 index 0000000000..55f76f1718 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/RandomAccessBody.java @@ -0,0 +1,31 @@ +/* + * 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 java.io.IOException; +import java.nio.channels.WritableByteChannel; + +/** + * A request body which supports random access to its contents. + */ +public interface RandomAccessBody extends Body { + + /** + * Transfers the specified chunk of bytes from this body to the specified channel. + * + * @param target The destination channel to transfer the body chunk to, must not be {@code null}. + * @return The non-negative number of bytes actually transferred. + * @throws IOException If the body chunk could not be transferred. + */ + long transferTo(WritableByteChannel target) throws IOException; +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/generator/BodyChunk.java b/client/src/main/java/org/asynchttpclient/request/body/generator/BodyChunk.java new file mode 100644 index 0000000000..c7af53232e --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/BodyChunk.java @@ -0,0 +1,28 @@ +/* + * 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.request.body.generator; + +import io.netty.buffer.ByteBuf; + +public final class BodyChunk { + public final boolean last; + public final ByteBuf buffer; + + BodyChunk(ByteBuf buffer, boolean last) { + this.buffer = buffer; + this.last = last; + } +} diff --git a/api/src/main/java/org/asynchttpclient/BodyGenerator.java b/client/src/main/java/org/asynchttpclient/request/body/generator/BodyGenerator.java similarity index 82% rename from api/src/main/java/org/asynchttpclient/BodyGenerator.java rename to client/src/main/java/org/asynchttpclient/request/body/generator/BodyGenerator.java index 9d92618768..835ef7bd60 100644 --- a/api/src/main/java/org/asynchttpclient/BodyGenerator.java +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/BodyGenerator.java @@ -10,23 +10,22 @@ * "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.generator; -package org.asynchttpclient; - -import java.io.IOException; +import org.asynchttpclient.request.body.Body; /** * Creates a request body. */ +@FunctionalInterface 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}. - * @throws IOException If the body could not be created. */ - Body createBody() throws IOException; + Body createBody(); } diff --git a/client/src/main/java/org/asynchttpclient/request/body/generator/BoundedQueueFeedableBodyGenerator.java b/client/src/main/java/org/asynchttpclient/request/body/generator/BoundedQueueFeedableBodyGenerator.java new file mode 100644 index 0000000000..8604fd39e6 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/BoundedQueueFeedableBodyGenerator.java @@ -0,0 +1,31 @@ +/* + * 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.request.body.generator; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +public final class BoundedQueueFeedableBodyGenerator extends QueueBasedFeedableBodyGenerator> { + + public BoundedQueueFeedableBodyGenerator(int capacity) { + super(new ArrayBlockingQueue<>(capacity, true)); + } + + @Override + protected boolean offer(BodyChunk chunk) throws InterruptedException { + return queue.offer(chunk); + } +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/generator/ByteArrayBodyGenerator.java b/client/src/main/java/org/asynchttpclient/request/body/generator/ByteArrayBodyGenerator.java new file mode 100644 index 0000000000..4cc6b55338 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/ByteArrayBodyGenerator.java @@ -0,0 +1,67 @@ +/* + * 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.generator; + +import io.netty.buffer.ByteBuf; +import org.asynchttpclient.request.body.Body; + +/** + * A {@link BodyGenerator} backed by a byte array. + */ +public final class ByteArrayBodyGenerator implements BodyGenerator { + + private final byte[] bytes; + + public ByteArrayBodyGenerator(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public Body createBody() { + return new ByteBody(); + } + + protected final class ByteBody implements Body { + private boolean eof; + private int lastPosition; + + @Override + public long getContentLength() { + return bytes.length; + } + + @Override + public BodyState transferTo(ByteBuf target) { + if (eof) { + return BodyState.STOP; + } + + final int remaining = bytes.length - lastPosition; + final int initialTargetWritableBytes = target.writableBytes(); + if (remaining <= initialTargetWritableBytes) { + target.writeBytes(bytes, lastPosition, remaining); + eof = true; + } else { + target.writeBytes(bytes, lastPosition, initialTargetWritableBytes); + lastPosition += initialTargetWritableBytes; + } + return BodyState.CONTINUE; + } + + @Override + public void close() { + lastPosition = 0; + eof = false; + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/generator/FeedListener.java b/client/src/main/java/org/asynchttpclient/request/body/generator/FeedListener.java new file mode 100644 index 0000000000..ce3e6f79ad --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/FeedListener.java @@ -0,0 +1,22 @@ +/* + * 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.request.body.generator; + +public interface FeedListener { + void onContentAdded(); + + void onError(Throwable t); +} 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 new file mode 100644 index 0000000000..1aa27f0ac8 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/FeedableBodyGenerator.java @@ -0,0 +1,29 @@ +/* + * 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. + * 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.request.body.generator; + +import io.netty.buffer.ByteBuf; + +/** + * {@link BodyGenerator} which may return just part of the payload at the time handler is requesting it. + * If it happens, client becomes responsible for providing the rest of the chunks. + */ +public interface FeedableBodyGenerator extends BodyGenerator { + + boolean feed(ByteBuf buffer, boolean isLast) throws Exception; + + void setListener(FeedListener listener); +} 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 new file mode 100644 index 0000000000..82bc02111c --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/FileBodyGenerator.java @@ -0,0 +1,56 @@ +/* + * 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.generator; + +import org.asynchttpclient.request.body.RandomAccessBody; + +import java.io.File; + +import static java.util.Objects.requireNonNull; + +/** + * Creates a request body from the contents of a file. + */ +public final class FileBodyGenerator implements BodyGenerator { + + private final File file; + private final long regionSeek; + private final long regionLength; + + public FileBodyGenerator(File file) { + this(file, 0L, file.length()); + } + + public FileBodyGenerator(File file, long regionSeek, long regionLength) { + this.file = requireNonNull(file, "file"); + this.regionLength = regionLength; + this.regionSeek = regionSeek; + } + + public File getFile() { + return file; + } + + public long getRegionLength() { + return regionLength; + } + + public long getRegionSeek() { + return regionSeek; + } + + @Override + public RandomAccessBody createBody() { + throw new UnsupportedOperationException("FileBodyGenerator.createBody isn't used, Netty direclt sends the file"); + } +} 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 new file mode 100644 index 0000000000..1f602dae40 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/InputStreamBodyGenerator.java @@ -0,0 +1,100 @@ +/* + * 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.generator; + +import io.netty.buffer.ByteBuf; +import org.asynchttpclient.request.body.Body; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; + +/** + * 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 work. + */ +public final class InputStreamBodyGenerator implements BodyGenerator { + + private static final Logger LOGGER = LoggerFactory.getLogger(InputStreamBody.class); + private final InputStream inputStream; + private final long contentLength; + + public InputStreamBodyGenerator(InputStream inputStream) { + this(inputStream, -1L); + } + + public InputStreamBodyGenerator(InputStream inputStream, long contentLength) { + this.inputStream = inputStream; + this.contentLength = contentLength; + } + + public InputStream getInputStream() { + return inputStream; + } + + public long getContentLength() { + return contentLength; + } + + @Override + public Body createBody() { + return new InputStreamBody(inputStream, contentLength); + } + + private static class InputStreamBody implements Body { + + private final InputStream inputStream; + private final long contentLength; + private byte[] chunk; + + private InputStreamBody(InputStream inputStream, long contentLength) { + this.inputStream = inputStream; + this.contentLength = contentLength; + } + + @Override + public long getContentLength() { + return contentLength; + } + + @Override + public BodyState transferTo(ByteBuf target) { + + // To be safe. + chunk = new byte[target.writableBytes() - 10]; + + int read = -1; + boolean write = false; + try { + read = inputStream.read(chunk); + } catch (IOException ex) { + LOGGER.warn("Unable to read", ex); + } + + if (read > 0) { + target.writeBytes(chunk, 0, read); + write = true; + } + return write ? BodyState.CONTINUE : BodyState.STOP; + } + + @Override + public void close() throws IOException { + inputStream.close(); + } + } +} + diff --git a/client/src/main/java/org/asynchttpclient/request/body/generator/PushBody.java b/client/src/main/java/org/asynchttpclient/request/body/generator/PushBody.java new file mode 100644 index 0000000000..72fb653332 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/PushBody.java @@ -0,0 +1,80 @@ +/* + * 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.request.body.generator; + +import io.netty.buffer.ByteBuf; +import org.asynchttpclient.request.body.Body; + +import java.util.Queue; + +public final class PushBody implements Body { + + private final Queue queue; + private BodyState state = BodyState.CONTINUE; + + public PushBody(Queue queue) { + this.queue = queue; + } + + @Override + public long getContentLength() { + return -1; + } + + @Override + public BodyState transferTo(final ByteBuf target) { + switch (state) { + case CONTINUE: + return readNextChunk(target); + case STOP: + return BodyState.STOP; + default: + throw new IllegalStateException("Illegal process state."); + } + } + + private BodyState readNextChunk(ByteBuf target) { + BodyState res = BodyState.SUSPEND; + while (target.isWritable() && state != BodyState.STOP) { + BodyChunk nextChunk = queue.peek(); + if (nextChunk == null) { + // Nothing in the queue. suspend stream if nothing was read. (reads == 0) + return res; + } else if (!nextChunk.buffer.isReadable() && !nextChunk.last) { + // skip empty buffers + queue.remove(); + } else { + res = BodyState.CONTINUE; + readChunk(target, nextChunk); + } + } + return res; + } + + private void readChunk(ByteBuf target, BodyChunk part) { + target.writeBytes(part.buffer); + if (!part.buffer.isReadable()) { + if (part.last) { + state = BodyState.STOP; + } + queue.remove(); + } + } + + @Override + public void close() { + } +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/generator/QueueBasedFeedableBodyGenerator.java b/client/src/main/java/org/asynchttpclient/request/body/generator/QueueBasedFeedableBodyGenerator.java new file mode 100644 index 0000000000..9bce479e25 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/QueueBasedFeedableBodyGenerator.java @@ -0,0 +1,52 @@ +/* + * 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.request.body.generator; + +import io.netty.buffer.ByteBuf; +import org.asynchttpclient.request.body.Body; + +import java.util.Queue; + +public abstract class QueueBasedFeedableBodyGenerator> implements FeedableBodyGenerator { + + protected final T queue; + private FeedListener listener; + + protected QueueBasedFeedableBodyGenerator(T queue) { + this.queue = queue; + } + + @Override + public Body createBody() { + return new PushBody(queue); + } + + protected abstract boolean offer(BodyChunk chunk) throws Exception; + + @Override + public boolean feed(final ByteBuf buffer, final boolean isLast) throws Exception { + boolean offered = offer(new BodyChunk(buffer, isLast)); + if (offered && listener != null) { + listener.onContentAdded(); + } + return offered; + } + + @Override + public void setListener(FeedListener listener) { + this.listener = listener; + } +} 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 new file mode 100755 index 0000000000..f55dcbe37a --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/UnboundedQueueFeedableBodyGenerator.java @@ -0,0 +1,30 @@ +/* + * 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. + * 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.request.body.generator; + +import java.util.concurrent.ConcurrentLinkedQueue; + +public final class UnboundedQueueFeedableBodyGenerator extends QueueBasedFeedableBodyGenerator> { + + public UnboundedQueueFeedableBodyGenerator() { + super(new ConcurrentLinkedQueue<>()); + } + + @Override + protected boolean offer(BodyChunk chunk) { + return queue.offer(chunk); + } +} 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 new file mode 100644 index 0000000000..203d37a2c5 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/ByteArrayPart.java @@ -0,0 +1,54 @@ +/* + * 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. + * 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.request.body.multipart; + +import java.nio.charset.Charset; + +import static java.util.Objects.requireNonNull; + +public class ByteArrayPart extends FileLikePart { + + private final byte[] bytes; + + public ByteArrayPart(String name, byte[] bytes) { + this(name, bytes, null); + } + + public ByteArrayPart(String name, byte[] bytes, String contentType) { + this(name, bytes, contentType, null); + } + + public ByteArrayPart(String name, byte[] bytes, String contentType, Charset charset) { + this(name, bytes, contentType, charset, null); + } + + public ByteArrayPart(String name, byte[] bytes, String contentType, Charset charset, String fileName) { + this(name, bytes, contentType, charset, fileName, null); + } + + public ByteArrayPart(String name, byte[] bytes, String contentType, Charset charset, String fileName, String contentId) { + this(name, bytes, contentType, charset, fileName, contentId, null); + } + + 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 = requireNonNull(bytes, "bytes"); + } + + public byte[] getBytes() { + return bytes; + } +} 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 new file mode 100644 index 0000000000..1d03ad22bb --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/FileLikePart.java @@ -0,0 +1,77 @@ +/* + * 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. + * 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.request.body.multipart; + +import jakarta.activation.MimetypesFileTypeMap; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +import static org.asynchttpclient.util.MiscUtils.withDefault; + +/** + * This class is an adaptation of the Apache HttpClient implementation + */ +public abstract class FileLikePart extends PartBase { + + private static final MimetypesFileTypeMap MIME_TYPES_FILE_TYPE_MAP; + + static { + try (InputStream is = FileLikePart.class.getResourceAsStream("ahc-mime.types")) { + MIME_TYPES_FILE_TYPE_MAP = new MimetypesFileTypeMap(is); + } catch (IOException e) { + throw new ExceptionInInitializerError(e); + } + } + + /** + * Default content encoding of file attachments. + */ + private final String fileName; + + /** + * FilePart Constructor. + * + * @param name the name for this part + * @param contentType the content type for this part, if {@code null} try to figure out from the fileName mime type + * @param charset the charset encoding for this part + * @param fileName the fileName + * @param contentId the content id + * @param transferEncoding the transfer encoding + */ + protected FileLikePart(String name, String contentType, Charset charset, String fileName, String contentId, String transferEncoding) { + super(name, + computeContentType(contentType, fileName), + charset, + contentId, + transferEncoding); + this.fileName = fileName; + } + + private static String computeContentType(String contentType, String fileName) { + return contentType != null ? contentType : MIME_TYPES_FILE_TYPE_MAP.getContentType(withDefault(fileName, "")); + } + + public String getFileName() { + return fileName; + } + + @Override + public String toString() { + return super.toString() + " filename=" + fileName; + } +} 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 new file mode 100644 index 0000000000..a156dd077a --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/FilePart.java @@ -0,0 +1,59 @@ +/* + * 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. + * 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.request.body.multipart; + +import java.io.File; +import java.nio.charset.Charset; + +public class FilePart extends FileLikePart { + + private final File file; + + public FilePart(String name, File file) { + this(name, file, null); + } + + public FilePart(String name, File file, String contentType) { + this(name, file, contentType, null); + } + + public FilePart(String name, File file, String contentType, Charset charset) { + this(name, file, contentType, charset, null); + } + + public FilePart(String name, File file, String contentType, Charset charset, String fileName) { + this(name, file, contentType, charset, fileName, null); + } + + public FilePart(String name, File file, String contentType, Charset charset, String fileName, String contentId) { + this(name, file, contentType, charset, fileName, contentId, null); + } + + public FilePart(String name, File file, String contentType, Charset charset, String fileName, String contentId, String transferEncoding) { + super(name, contentType, charset, fileName != null ? fileName : file.getName(), contentId, transferEncoding); + if (!file.isFile()) { + throw new IllegalArgumentException("File is not a normal file " + file.getAbsolutePath()); + } + if (!file.canRead()) { + throw new IllegalArgumentException("File is not readable " + file.getAbsolutePath()); + } + this.file = file; + } + + public File getFile() { + return file; + } +} 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 new file mode 100644 index 0000000000..aa14c979ac --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/InputStreamPart.java @@ -0,0 +1,62 @@ +/* + * 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.request.body.multipart; + +import java.io.InputStream; +import java.nio.charset.Charset; + +import static java.util.Objects.requireNonNull; + +public class InputStreamPart extends FileLikePart { + + private final InputStream inputStream; + private final long contentLength; + + public InputStreamPart(String name, InputStream inputStream, String fileName) { + this(name, inputStream, fileName, -1); + } + + public InputStreamPart(String name, InputStream inputStream, String fileName, long contentLength) { + this(name, inputStream, fileName, contentLength, null); + } + + public InputStreamPart(String name, InputStream inputStream, String fileName, long contentLength, String contentType) { + this(name, inputStream, fileName, contentLength, contentType, null); + } + + public InputStreamPart(String name, InputStream inputStream, String fileName, long contentLength, String contentType, Charset charset) { + this(name, inputStream, fileName, contentLength, contentType, charset, null); + } + + public InputStreamPart(String name, InputStream inputStream, String fileName, long contentLength, String contentType, Charset charset, String contentId) { + this(name, inputStream, fileName, contentLength, contentType, charset, contentId, null); + } + + 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 = requireNonNull(inputStream, "inputStream"); + this.contentLength = contentLength; + } + + public InputStream getInputStream() { + return inputStream; + } + + public long getContentLength() { + return 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 new file mode 100644 index 0000000000..a1fbb60876 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/MultipartBody.java @@ -0,0 +1,139 @@ +/* + * 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.request.body.multipart; + +import io.netty.buffer.ByteBuf; +import org.asynchttpclient.netty.request.body.BodyChunkedInput; +import org.asynchttpclient.request.body.RandomAccessBody; +import org.asynchttpclient.request.body.multipart.part.MultipartPart; +import org.asynchttpclient.request.body.multipart.part.MultipartState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.channels.WritableByteChannel; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import static java.util.Objects.requireNonNull; +import static org.asynchttpclient.util.MiscUtils.closeSilently; + +public class MultipartBody implements RandomAccessBody { + + private static final Logger LOGGER = LoggerFactory.getLogger(MultipartBody.class); + + private final List> parts; + private final String contentType; + private final byte[] boundary; + private final long contentLength; + private int currentPartIndex; + private boolean done; + private final AtomicBoolean closed = new AtomicBoolean(); + + public MultipartBody(List> parts, String contentType, byte[] boundary) { + this.boundary = boundary; + this.contentType = contentType; + this.parts = requireNonNull(parts, "parts"); + contentLength = computeContentLength(); + } + + private long computeContentLength() { + try { + long total = 0; + for (MultipartPart part : parts) { + long l = part.length(); + if (l < 0) { + return -1; + } + total += l; + } + return total; + } catch (Exception e) { + LOGGER.error("An exception occurred while getting the length of the parts", e); + return 0L; + } + } + + @Override + public void close() { + if (closed.compareAndSet(false, true)) { + for (MultipartPart part : parts) { + closeSilently(part); + } + } + } + + @Override + public long getContentLength() { + return contentLength; + } + + public String getContentType() { + return contentType; + } + + public byte[] getBoundary() { + return boundary; + } + + // Regular Body API + @Override + public BodyState transferTo(ByteBuf target) throws IOException { + if (done) { + return BodyState.STOP; + } + + while (target.isWritable() && !done) { + MultipartPart currentPart = parts.get(currentPartIndex); + currentPart.transferTo(target); + + if (currentPart.getState() == MultipartState.DONE) { + currentPartIndex++; + if (currentPartIndex == parts.size()) { + done = true; + } + } + } + + return BodyState.CONTINUE; + } + + // RandomAccessBody API, suited for HTTP but not for HTTPS (zero-copy) + @Override + public long transferTo(WritableByteChannel target) throws IOException { + if (done) { + return -1L; + } + + long transferred = 0L; + boolean slowTarget = false; + + while (transferred < BodyChunkedInput.DEFAULT_CHUNK_SIZE && !done && !slowTarget) { + MultipartPart currentPart = parts.get(currentPartIndex); + transferred += currentPart.transferTo(target); + slowTarget = currentPart.isTargetSlow(); + + if (currentPart.getState() == MultipartState.DONE) { + currentPartIndex++; + if (currentPartIndex == parts.size()) { + done = true; + } + } + } + + return transferred; + } +} 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 new file mode 100644 index 0000000000..eb7314f4bc --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/MultipartUtils.java @@ -0,0 +1,100 @@ +/* + * 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.request.body.multipart; + +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpHeaders; +import org.asynchttpclient.request.body.multipart.part.ByteArrayMultipartPart; +import org.asynchttpclient.request.body.multipart.part.FileMultipartPart; +import org.asynchttpclient.request.body.multipart.part.InputStreamMultipartPart; +import org.asynchttpclient.request.body.multipart.part.MessageEndMultipartPart; +import org.asynchttpclient.request.body.multipart.part.MultipartPart; +import org.asynchttpclient.request.body.multipart.part.StringMultipartPart; + +import java.util.ArrayList; +import java.util.List; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static java.nio.charset.StandardCharsets.US_ASCII; +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; + +public final class MultipartUtils { + + private MultipartUtils() { + // Prevent outside initialization + } + + /** + * Creates a new multipart entity containing the given parts. + * + * @param parts the parts to include. + * @param requestHeaders the request headers + * @return a MultipartBody + */ + public static MultipartBody newMultipartBody(List parts, HttpHeaders requestHeaders) { + requireNonNull(parts, "parts"); + + byte[] boundary; + String contentType; + + String contentTypeHeader = requestHeaders.get(CONTENT_TYPE); + if (isNonEmpty(contentTypeHeader)) { + int boundaryLocation = contentTypeHeader.indexOf("boundary="); + if (boundaryLocation != -1) { + // boundary defined in existing Content-Type + contentType = contentTypeHeader; + boundary = contentTypeHeader.substring(boundaryLocation + "boundary=".length()).trim().getBytes(US_ASCII); + } else { + // generate boundary and append it to existing Content-Type + boundary = computeMultipartBoundary(); + contentType = patchContentTypeWithBoundaryAttribute(contentTypeHeader, boundary); + } + } else { + boundary = computeMultipartBoundary(); + contentType = patchContentTypeWithBoundaryAttribute(HttpHeaderValues.MULTIPART_FORM_DATA.toString(), boundary); + } + + List> multipartParts = generateMultipartParts(parts, boundary); + return new MultipartBody(multipartParts, contentType, boundary); + } + + public static List> generateMultipartParts(List parts, byte[] boundary) { + List> multipartParts = new ArrayList<>(parts.size()); + for (Part part : parts) { + if (part instanceof FilePart) { + multipartParts.add(new FileMultipartPart((FilePart) part, boundary)); + + } else if (part instanceof ByteArrayPart) { + multipartParts.add(new ByteArrayMultipartPart((ByteArrayPart) part, boundary)); + + } else if (part instanceof StringPart) { + multipartParts.add(new StringMultipartPart((StringPart) part, boundary)); + + } else if (part instanceof InputStreamPart) { + multipartParts.add(new InputStreamMultipartPart((InputStreamPart) part, boundary)); + + } else { + throw new IllegalArgumentException("Unknown part type: " + part); + } + } + // add an extra fake part for terminating the message + multipartParts.add(new MessageEndMultipartPart(boundary)); + return multipartParts; + } +} 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 new file mode 100644 index 0000000000..19266eb25f --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/Part.java @@ -0,0 +1,72 @@ +/* + * 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. + * 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.request.body.multipart; + +import org.asynchttpclient.Param; + +import java.nio.charset.Charset; +import java.util.List; + +public interface Part { + + /** + * Return the name of this part. + * + * @return The name. + */ + String getName(); + + /** + * Returns the content type of this part. + * + * @return the content type, or {@code null} to exclude the content + * type header + */ + String getContentType(); + + /** + * Return the character encoding of this part. + * + * @return the character encoding, or {@code null} to exclude the + * character encoding header + */ + Charset getCharset(); + + /** + * Return the transfer encoding of this part. + * + * @return the transfer encoding, or {@code null} to exclude the + * transfer encoding header + */ + String getTransferEncoding(); + + /** + * Return the content ID of this part. + * + * @return the content ID, or {@code null} to exclude the content ID + * header + */ + String getContentId(); + + /** + * Gets the disposition-type to be used in Content-Disposition header + * + * @return the disposition-type + */ + String getDispositionType(); + + List getCustomHeaders(); +} 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 new file mode 100644 index 0000000000..65e78286f0 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/PartBase.java @@ -0,0 +1,138 @@ +/* + * 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. + * 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.request.body.multipart; + +import org.asynchttpclient.Param; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +public abstract class PartBase implements Part { + + /** + * The name of the form field, part of the Content-Disposition header + */ + private final String name; + + /** + * The main part of the Content-Type header + */ + private final String contentType; + + /** + * The charset (part of Content-Type header) + */ + private final Charset charset; + + /** + * The Content-Transfer-Encoding header value. + */ + private final String transferEncoding; + + /** + * The Content-Id + */ + private final String contentId; + + /** + * The disposition type (part of Content-Disposition) + */ + private String dispositionType; + + /** + * Additional part headers + */ + private List customHeaders; + + /** + * Constructor. + * + * @param name The name of the part, or {@code null} + * @param contentType The content type, or {@code null} + * @param charset The character encoding, or {@code null} + * @param contentId The content id, or {@code null} + * @param transferEncoding The transfer encoding, or {@code null} + */ + protected PartBase(String name, String contentType, Charset charset, String contentId, String transferEncoding) { + this.name = name; + this.contentType = contentType; + this.charset = charset; + this.contentId = contentId; + this.transferEncoding = transferEncoding; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public Charset getCharset() { + return charset; + } + + @Override + public String getTransferEncoding() { + return transferEncoding; + } + + @Override + public String getContentId() { + return contentId; + } + + @Override + public String getDispositionType() { + return dispositionType; + } + + public void setDispositionType(String dispositionType) { + this.dispositionType = dispositionType; + } + + @Override + public List getCustomHeaders() { + return customHeaders; + } + + public void setCustomHeaders(List customHeaders) { + this.customHeaders = customHeaders; + } + + public void addCustomHeader(String name, String value) { + if (customHeaders == null) { + customHeaders = new ArrayList<>(2); + } + customHeaders.add(new Param(name, value)); + } + + @Override + public String toString() { + return getClass().getSimpleName() + + " name=" + getName() + + " contentType=" + getContentType() + + " charset=" + getCharset() + + " transferEncoding=" + getTransferEncoding() + + " contentId=" + getContentId() + + " dispositionType=" + getDispositionType(); + } +} 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 new file mode 100644 index 0000000000..05c958d35d --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/StringPart.java @@ -0,0 +1,71 @@ +/* + * 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. + * 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.request.body.multipart; + +import java.nio.charset.Charset; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; +import static org.asynchttpclient.util.MiscUtils.withDefault; + +public class StringPart extends PartBase { + + /** + * Default charset of string parameters + */ + private static final Charset DEFAULT_CHARSET = UTF_8; + + /** + * Contents of this StringPart. + */ + private final String value; + + public StringPart(String name, String value) { + this(name, value, null); + } + + public StringPart(String name, String value, String contentType) { + this(name, value, contentType, null); + } + + public StringPart(String name, String value, String contentType, Charset charset) { + this(name, value, contentType, charset, null); + } + + public StringPart(String name, String value, String contentType, Charset charset, String contentId) { + this(name, value, contentType, charset, contentId, null); + } + + public StringPart(String name, String value, String contentType, Charset charset, String contentId, String transferEncoding) { + super(name, contentType, charsetOrDefault(charset), contentId, transferEncoding); + requireNonNull(value, "value"); + + // See RFC 2048, 2.8. "8bit Data" + if (value.indexOf(0) != -1) { + throw new IllegalArgumentException("NULs may not be present in string parts"); + } + + this.value = value; + } + + private static Charset charsetOrDefault(Charset charset) { + return withDefault(charset, DEFAULT_CHARSET); + } + + public String getValue() { + return value; + } +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/part/ByteArrayMultipartPart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/ByteArrayMultipartPart.java new file mode 100644 index 0000000000..063afcf2a2 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/ByteArrayMultipartPart.java @@ -0,0 +1,54 @@ +/* + * 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.request.body.multipart.part; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.asynchttpclient.request.body.multipart.ByteArrayPart; + +import java.io.IOException; +import java.nio.channels.WritableByteChannel; + +public class ByteArrayMultipartPart extends FileLikeMultipartPart { + + private final ByteBuf contentBuffer; + + public ByteArrayMultipartPart(ByteArrayPart part, byte[] boundary) { + super(part, boundary); + contentBuffer = Unpooled.wrappedBuffer(part.getBytes()); + } + + @Override + protected long getContentLength() { + return part.getBytes().length; + } + + @Override + protected long transferContentTo(ByteBuf target) { + return transfer(contentBuffer, target, MultipartState.POST_CONTENT); + } + + @Override + protected long transferContentTo(WritableByteChannel target) throws IOException { + return transfer(contentBuffer, target, MultipartState.POST_CONTENT); + } + + @Override + public void close() { + super.close(); + contentBuffer.release(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/part/FileLikeMultipartPart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/FileLikeMultipartPart.java new file mode 100644 index 0000000000..659906f26f --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/FileLikeMultipartPart.java @@ -0,0 +1,44 @@ +/* + * 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.request.body.multipart.part; + +import org.asynchttpclient.request.body.multipart.FileLikePart; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; + +public abstract class FileLikeMultipartPart extends MultipartPart { + + /** + * Attachment's file name as a byte array + */ + private static final byte[] FILE_NAME_BYTES = "; filename=".getBytes(US_ASCII); + + FileLikeMultipartPart(T part, byte[] boundary) { + super(part, boundary); + } + + @Override + protected void visitDispositionHeader(PartVisitor visitor) { + super.visitDispositionHeader(visitor); + if (part.getFileName() != null) { + visitor.withBytes(FILE_NAME_BYTES); + visitor.withByte(QUOTE_BYTE); + visitor.withBytes(part.getFileName().getBytes(part.getCharset() != null ? part.getCharset() : UTF_8)); + visitor.withByte(QUOTE_BYTE); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/part/FileMultipartPart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/FileMultipartPart.java new file mode 100644 index 0000000000..65bdd58ca0 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/FileMultipartPart.java @@ -0,0 +1,101 @@ +/* + * 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.request.body.multipart.part; + +import io.netty.buffer.ByteBuf; +import org.asynchttpclient.netty.request.body.BodyChunkedInput; +import org.asynchttpclient.request.body.multipart.FilePart; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.nio.channels.WritableByteChannel; + +import static org.asynchttpclient.util.MiscUtils.closeSilently; + +public class FileMultipartPart extends FileLikeMultipartPart { + + private final long length; + private FileChannel channel; + private long position; + + public FileMultipartPart(FilePart part, byte[] boundary) { + super(part, boundary); + File file = part.getFile(); + if (!file.exists()) { + throw new IllegalArgumentException("File part doesn't exist: " + file.getAbsolutePath()); + } + if (!file.canRead()) { + throw new IllegalArgumentException("File part can't be read: " + file.getAbsolutePath()); + } + length = file.length(); + } + + private FileChannel getChannel() throws IOException { + if (channel == null) { + channel = new RandomAccessFile(part.getFile(), "r").getChannel(); + } + return channel; + } + + @Override + protected long getContentLength() { + return length; + } + + @Override + protected long transferContentTo(ByteBuf target) throws IOException { + // can return -1 if file is empty or FileChannel was closed + int transferred = target.writeBytes(getChannel(), target.writableBytes()); + if (transferred > 0) { + position += transferred; + } + if (position == length || transferred < 0) { + state = MultipartState.POST_CONTENT; + if (channel.isOpen()) { + channel.close(); + } + } + return transferred; + } + + @Override + protected long transferContentTo(WritableByteChannel target) throws IOException { + // WARN: don't use channel.position(), it's always 0 here + // from FileChannel javadoc: "This method does not modify this channel's + // position." + long transferred = getChannel().transferTo(position, BodyChunkedInput.DEFAULT_CHUNK_SIZE, target); + if (transferred > 0) { + position += transferred; + } + if (position == length || transferred < 0) { + state = MultipartState.POST_CONTENT; + if (channel.isOpen()) { + channel.close(); + } + } else { + slowTarget = true; + } + return transferred; + } + + @Override + public void close() { + super.close(); + closeSilently(channel); + } +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/part/InputStreamMultipartPart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/InputStreamMultipartPart.java new file mode 100644 index 0000000000..cf1acb0a78 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/InputStreamMultipartPart.java @@ -0,0 +1,106 @@ +/* + * 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.request.body.multipart.part; + +import io.netty.buffer.ByteBuf; +import org.asynchttpclient.netty.request.body.BodyChunkedInput; +import org.asynchttpclient.request.body.multipart.InputStreamPart; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +import static org.asynchttpclient.util.MiscUtils.closeSilently; + +public class InputStreamMultipartPart extends FileLikeMultipartPart { + + private long position; + private ByteBuffer buffer; + private ReadableByteChannel channel; + + public InputStreamMultipartPart(InputStreamPart part, byte[] boundary) { + super(part, boundary); + } + + private ByteBuffer getBuffer() { + if (buffer == null) { + buffer = ByteBuffer.allocateDirect(BodyChunkedInput.DEFAULT_CHUNK_SIZE); + } + return buffer; + } + + private ReadableByteChannel getChannel() { + if (channel == null) { + channel = Channels.newChannel(part.getInputStream()); + } + return channel; + } + + @Override + protected long getContentLength() { + return part.getContentLength(); + } + + @Override + protected long transferContentTo(ByteBuf target) throws IOException { + InputStream inputStream = part.getInputStream(); + int transferred = target.writeBytes(inputStream, target.writableBytes()); + if (transferred > 0) { + position += transferred; + } + if (position == getContentLength() || transferred < 0) { + state = MultipartState.POST_CONTENT; + inputStream.close(); + } + return transferred; + } + + @Override + protected long transferContentTo(WritableByteChannel target) throws IOException { + ReadableByteChannel channel = getChannel(); + ByteBuffer buffer = getBuffer(); + + int transferred = 0; + int read = channel.read(buffer); + + if (read > 0) { + buffer.flip(); + while (buffer.hasRemaining()) { + transferred += target.write(buffer); + } + buffer.compact(); + position += transferred; + } + if (position == getContentLength() || read < 0) { + state = MultipartState.POST_CONTENT; + if (channel.isOpen()) { + channel.close(); + } + } + + return transferred; + } + + @Override + public void close() { + super.close(); + closeSilently(part.getInputStream()); + closeSilently(channel); + } +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/part/MessageEndMultipartPart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/MessageEndMultipartPart.java new file mode 100644 index 0000000000..4083f2bdda --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/MessageEndMultipartPart.java @@ -0,0 +1,97 @@ +/* + * 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.request.body.multipart.part; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import org.asynchttpclient.request.body.multipart.FileLikePart; + +import java.io.IOException; +import java.nio.channels.WritableByteChannel; + +public class MessageEndMultipartPart extends MultipartPart { + + // lazy + private ByteBuf contentBuffer; + + public MessageEndMultipartPart(byte[] boundary) { + super(null, boundary); + state = MultipartState.PRE_CONTENT; + } + + @Override + public long transferTo(ByteBuf target) { + return transfer(lazyLoadContentBuffer(), target, MultipartState.DONE); + } + + @Override + public long transferTo(WritableByteChannel target) throws IOException { + slowTarget = false; + return transfer(lazyLoadContentBuffer(), target, MultipartState.DONE); + } + + private ByteBuf lazyLoadContentBuffer() { + if (contentBuffer == null) { + contentBuffer = ByteBufAllocator.DEFAULT.buffer((int) getContentLength()); + contentBuffer.writeBytes(EXTRA_BYTES).writeBytes(boundary).writeBytes(EXTRA_BYTES).writeBytes(CRLF_BYTES); + } + return contentBuffer; + } + + @Override + protected int computePreContentLength() { + return 0; + } + + @Override + protected ByteBuf computePreContentBytes(int preContentLength) { + return Unpooled.EMPTY_BUFFER; + } + + @Override + protected int computePostContentLength() { + return 0; + } + + @Override + protected ByteBuf computePostContentBytes(int postContentLength) { + return Unpooled.EMPTY_BUFFER; + } + + @Override + protected long getContentLength() { + return EXTRA_BYTES.length + boundary.length + EXTRA_BYTES.length + CRLF_BYTES.length; + } + + @Override + protected long transferContentTo(ByteBuf target) { + throw new UnsupportedOperationException("Not supposed to be called"); + } + + @Override + protected long transferContentTo(WritableByteChannel target) { + throw new UnsupportedOperationException("Not supposed to be called"); + } + + @Override + public void close() { + super.close(); + if (contentBuffer != null) { + contentBuffer.release(); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/part/MultipartPart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/MultipartPart.java new file mode 100644 index 0000000000..2441980449 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/MultipartPart.java @@ -0,0 +1,333 @@ +/* + * 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.request.body.multipart.part; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import org.asynchttpclient.Param; +import org.asynchttpclient.request.body.multipart.PartBase; +import org.asynchttpclient.request.body.multipart.part.PartVisitor.ByteBufVisitor; +import org.asynchttpclient.request.body.multipart.part.PartVisitor.CounterPartVisitor; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; + +public abstract class MultipartPart implements Closeable { + + /** + * Content disposition as a byte + */ + static final byte QUOTE_BYTE = '\"'; + /** + * Carriage return/linefeed as a byte array + */ + protected static final byte[] CRLF_BYTES = "\r\n".getBytes(US_ASCII); + /** + * Extra characters as a byte array + */ + protected static final byte[] EXTRA_BYTES = "--".getBytes(US_ASCII); + + /** + * Content disposition as a byte array + */ + private static final byte[] CONTENT_DISPOSITION_BYTES = "Content-Disposition: ".getBytes(US_ASCII); + + /** + * form-data as a byte array + */ + private static final byte[] FORM_DATA_DISPOSITION_TYPE_BYTES = "form-data".getBytes(US_ASCII); + + /** + * name as a byte array + */ + private static final byte[] NAME_BYTES = "; name=".getBytes(US_ASCII); + + /** + * Content type header as a byte array + */ + private static final byte[] CONTENT_TYPE_BYTES = "Content-Type: ".getBytes(US_ASCII); + + /** + * Content charset as a byte array + */ + private static final byte[] CHARSET_BYTES = "; charset=".getBytes(US_ASCII); + + /** + * Content type header as a byte array + */ + private static final byte[] CONTENT_TRANSFER_ENCODING_BYTES = "Content-Transfer-Encoding: ".getBytes(US_ASCII); + + /** + * Content type header as a byte array + */ + private static final byte[] HEADER_NAME_VALUE_SEPARATOR_BYTES = ": ".getBytes(US_ASCII); + + /** + * Content type header as a byte array + */ + private static final byte[] CONTENT_ID_BYTES = "Content-ID: ".getBytes(US_ASCII); + + protected final T part; + protected final byte[] boundary; + + private final int preContentLength; + private final int postContentLength; + protected MultipartState state; + boolean slowTarget; + + // lazy + private ByteBuf preContentBuffer; + private ByteBuf postContentBuffer; + + MultipartPart(T part, byte[] boundary) { + this.part = part; + this.boundary = boundary; + preContentLength = computePreContentLength(); + postContentLength = computePostContentLength(); + state = MultipartState.PRE_CONTENT; + } + + public long length() { + long contentLength = getContentLength(); + if (contentLength < 0) { + return contentLength; + } + return preContentLength + postContentLength + getContentLength(); + } + + public MultipartState getState() { + return state; + } + + public boolean isTargetSlow() { + return slowTarget; + } + + public long transferTo(ByteBuf target) throws IOException { + switch (state) { + case DONE: + return 0L; + case PRE_CONTENT: + return transfer(lazyLoadPreContentBuffer(), target, MultipartState.CONTENT); + case CONTENT: + return transferContentTo(target); + case POST_CONTENT: + return transfer(lazyLoadPostContentBuffer(), target, MultipartState.DONE); + default: + throw new IllegalStateException("Unknown state " + state); + } + } + + public long transferTo(WritableByteChannel target) throws IOException { + slowTarget = false; + switch (state) { + case DONE: + return 0L; + case PRE_CONTENT: + return transfer(lazyLoadPreContentBuffer(), target, MultipartState.CONTENT); + case CONTENT: + return transferContentTo(target); + case POST_CONTENT: + return transfer(lazyLoadPostContentBuffer(), target, MultipartState.DONE); + default: + throw new IllegalStateException("Unknown state " + state); + } + } + + private ByteBuf lazyLoadPreContentBuffer() { + if (preContentBuffer == null) { + preContentBuffer = computePreContentBytes(preContentLength); + } + return preContentBuffer; + } + + private ByteBuf lazyLoadPostContentBuffer() { + if (postContentBuffer == null) { + postContentBuffer = computePostContentBytes(postContentLength); + } + return postContentBuffer; + } + + @Override + public void close() { + if (preContentBuffer != null) { + preContentBuffer.release(); + } + if (postContentBuffer != null) { + postContentBuffer.release(); + } + } + + protected abstract long getContentLength(); + + protected abstract long transferContentTo(ByteBuf target) throws IOException; + + protected abstract long transferContentTo(WritableByteChannel target) throws IOException; + + protected long transfer(ByteBuf source, ByteBuf target, MultipartState sourceFullyWrittenState) { + + int sourceRemaining = source.readableBytes(); + int targetRemaining = target.writableBytes(); + + if (sourceRemaining <= targetRemaining) { + target.writeBytes(source); + state = sourceFullyWrittenState; + return sourceRemaining; + } else { + target.writeBytes(source, targetRemaining); + return targetRemaining; + } + } + + protected long transfer(ByteBuf source, WritableByteChannel target, MultipartState sourceFullyWrittenState) throws IOException { + + int transferred = 0; + if (target instanceof GatheringByteChannel) { + transferred = source.readBytes((GatheringByteChannel) target, source.readableBytes()); + } else { + for (ByteBuffer byteBuffer : source.nioBuffers()) { + int len = byteBuffer.remaining(); + int written = target.write(byteBuffer); + transferred += written; + if (written != len) { + // couldn't write full buffer, exit loop + break; + } + } + // assume this is a basic single ByteBuf + source.readerIndex(source.readerIndex() + transferred); + } + + if (source.isReadable()) { + slowTarget = true; + } else { + state = sourceFullyWrittenState; + } + return transferred; + } + + protected int computePreContentLength() { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + visitPreContent(counterVisitor); + return counterVisitor.getCount(); + } + + protected ByteBuf computePreContentBytes(int preContentLength) { + ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(preContentLength); + ByteBufVisitor bytesVisitor = new ByteBufVisitor(buffer); + visitPreContent(bytesVisitor); + return buffer; + } + + protected int computePostContentLength() { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + visitPostContent(counterVisitor); + return counterVisitor.getCount(); + } + + protected ByteBuf computePostContentBytes(int postContentLength) { + ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(postContentLength); + ByteBufVisitor bytesVisitor = new ByteBufVisitor(buffer); + visitPostContent(bytesVisitor); + return buffer; + } + + protected void visitStart(PartVisitor visitor) { + visitor.withBytes(EXTRA_BYTES); + visitor.withBytes(boundary); + } + + protected void visitDispositionHeader(PartVisitor visitor) { + visitor.withBytes(CRLF_BYTES); + visitor.withBytes(CONTENT_DISPOSITION_BYTES); + visitor.withBytes(part.getDispositionType() != null ? part.getDispositionType().getBytes(US_ASCII) : FORM_DATA_DISPOSITION_TYPE_BYTES); + if (part.getName() != null) { + visitor.withBytes(NAME_BYTES); + visitor.withByte(QUOTE_BYTE); + visitor.withBytes(part.getName().getBytes(US_ASCII)); + visitor.withByte(QUOTE_BYTE); + } + } + + protected void visitContentTypeHeader(PartVisitor visitor) { + String contentType = part.getContentType(); + if (contentType != null) { + visitor.withBytes(CRLF_BYTES); + visitor.withBytes(CONTENT_TYPE_BYTES); + visitor.withBytes(contentType.getBytes(US_ASCII)); + Charset charSet = part.getCharset(); + if (charSet != null) { + visitor.withBytes(CHARSET_BYTES); + visitor.withBytes(part.getCharset().name().getBytes(US_ASCII)); + } + } + } + + protected void visitTransferEncodingHeader(PartVisitor visitor) { + String transferEncoding = part.getTransferEncoding(); + if (transferEncoding != null) { + visitor.withBytes(CRLF_BYTES); + visitor.withBytes(CONTENT_TRANSFER_ENCODING_BYTES); + visitor.withBytes(transferEncoding.getBytes(US_ASCII)); + } + } + + protected void visitContentIdHeader(PartVisitor visitor) { + String contentId = part.getContentId(); + if (contentId != null) { + visitor.withBytes(CRLF_BYTES); + visitor.withBytes(CONTENT_ID_BYTES); + visitor.withBytes(contentId.getBytes(US_ASCII)); + } + } + + protected void visitCustomHeaders(PartVisitor visitor) { + if (isNonEmpty(part.getCustomHeaders())) { + for (Param param : part.getCustomHeaders()) { + visitor.withBytes(CRLF_BYTES); + visitor.withBytes(param.getName().getBytes(US_ASCII)); + visitor.withBytes(HEADER_NAME_VALUE_SEPARATOR_BYTES); + visitor.withBytes(param.getValue().getBytes(US_ASCII)); + } + } + } + + protected void visitEndOfHeaders(PartVisitor visitor) { + visitor.withBytes(CRLF_BYTES); + visitor.withBytes(CRLF_BYTES); + } + + protected void visitPreContent(PartVisitor visitor) { + visitStart(visitor); + visitDispositionHeader(visitor); + visitContentTypeHeader(visitor); + visitTransferEncodingHeader(visitor); + visitContentIdHeader(visitor); + visitCustomHeaders(visitor); + visitEndOfHeaders(visitor); + } + + protected void visitPostContent(PartVisitor visitor) { + visitor.withBytes(CRLF_BYTES); + } +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/part/MultipartState.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/MultipartState.java new file mode 100644 index 0000000000..a4f6c402fd --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/MultipartState.java @@ -0,0 +1,23 @@ +/* + * 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.request.body.multipart.part; + +public enum MultipartState { + PRE_CONTENT, + CONTENT, + POST_CONTENT, + DONE +} 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 new file mode 100644 index 0000000000..8e9029c4fc --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/PartVisitor.java @@ -0,0 +1,62 @@ +/* + * 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. + * 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.request.body.multipart.part; + +import io.netty.buffer.ByteBuf; + +public interface PartVisitor { + + void withBytes(byte[] bytes); + + void withByte(byte b); + + class CounterPartVisitor implements PartVisitor { + + private int count; + + @Override + public void withBytes(byte[] bytes) { + count += bytes.length; + } + + @Override + public void withByte(byte b) { + count++; + } + + public int getCount() { + return count; + } + } + + class ByteBufVisitor implements PartVisitor { + private final ByteBuf target; + + public ByteBufVisitor(ByteBuf target) { + this.target = target; + } + + @Override + public void withBytes(byte[] bytes) { + target.writeBytes(bytes); + } + + @Override + public void withByte(byte b) { + target.writeByte(b); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/request/body/multipart/part/StringMultipartPart.java b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/StringMultipartPart.java new file mode 100644 index 0000000000..e3db5a0954 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/request/body/multipart/part/StringMultipartPart.java @@ -0,0 +1,54 @@ +/* + * 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.request.body.multipart.part; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.asynchttpclient.request.body.multipart.StringPart; + +import java.io.IOException; +import java.nio.channels.WritableByteChannel; + +public class StringMultipartPart extends MultipartPart { + + private final ByteBuf contentBuffer; + + public StringMultipartPart(StringPart part, byte[] boundary) { + super(part, boundary); + contentBuffer = Unpooled.wrappedBuffer(part.getValue().getBytes(part.getCharset())); + } + + @Override + protected long getContentLength() { + return contentBuffer.capacity(); + } + + @Override + protected long transferContentTo(ByteBuf target) { + return transfer(contentBuffer, target, MultipartState.POST_CONTENT); + } + + @Override + protected long transferContentTo(WritableByteChannel target) throws IOException { + return transfer(contentBuffer, target, MultipartState.POST_CONTENT); + } + + @Override + public void close() { + super.close(); + contentBuffer.release(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/resolver/RequestHostnameResolver.java b/client/src/main/java/org/asynchttpclient/resolver/RequestHostnameResolver.java new file mode 100644 index 0000000000..b6330cf14a --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/resolver/RequestHostnameResolver.java @@ -0,0 +1,86 @@ +/* + * 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.resolver; + +import io.netty.resolver.NameResolver; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.ImmediateEventExecutor; +import io.netty.util.concurrent.Promise; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.netty.SimpleFutureListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; + +public enum RequestHostnameResolver { + + INSTANCE; + + private static final Logger LOGGER = LoggerFactory.getLogger(RequestHostnameResolver.class); + + public Future> resolve(NameResolver nameResolver, InetSocketAddress unresolvedAddress, AsyncHandler asyncHandler) { + final String hostname = unresolvedAddress.getHostString(); + final int port = unresolvedAddress.getPort(); + final Promise> promise = ImmediateEventExecutor.INSTANCE.newPromise(); + + try { + asyncHandler.onHostnameResolutionAttempt(hostname); + } catch (Exception e) { + LOGGER.error("onHostnameResolutionAttempt crashed", e); + promise.tryFailure(e); + return promise; + } + + final Future> whenResolved = nameResolver.resolveAll(hostname); + + whenResolved.addListener(new SimpleFutureListener>() { + + @Override + protected void onSuccess(List value) { + ArrayList socketAddresses = new ArrayList<>(value.size()); + for (InetAddress a : value) { + socketAddresses.add(new InetSocketAddress(a, port)); + } + try { + asyncHandler.onHostnameResolutionSuccess(hostname, socketAddresses); + } catch (Exception e) { + LOGGER.error("onHostnameResolutionSuccess crashed", e); + promise.tryFailure(e); + return; + } + promise.trySuccess(socketAddresses); + } + + @Override + protected void onFailure(Throwable t) { + try { + asyncHandler.onHostnameResolutionFailure(hostname, t); + } catch (Exception e) { + LOGGER.error("onHostnameResolutionFailure crashed", e); + promise.tryFailure(e); + return; + } + promise.tryFailure(t); + } + }); + + return promise; + } +} diff --git a/client/src/main/java/org/asynchttpclient/spnego/NamePasswordCallbackHandler.java b/client/src/main/java/org/asynchttpclient/spnego/NamePasswordCallbackHandler.java new file mode 100644 index 0000000000..164ee54711 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/spnego/NamePasswordCallbackHandler.java @@ -0,0 +1,95 @@ +/* + * 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.spnego; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import java.io.IOException; +import java.lang.reflect.Method; + +public class NamePasswordCallbackHandler implements CallbackHandler { + private final Logger log = LoggerFactory.getLogger(getClass()); + private static final String PASSWORD_CALLBACK_NAME = "setObject"; + private static final Class[] PASSWORD_CALLBACK_TYPES = new Class[]{Object.class, char[].class, String.class}; + + private final String username; + private final @Nullable String password; + private final @Nullable String passwordCallbackName; + + public NamePasswordCallbackHandler(String username, @Nullable String password) { + this(username, password, null); + } + + public NamePasswordCallbackHandler(String username, @Nullable String password, @Nullable String passwordCallbackName) { + this.username = username; + this.password = password; + this.passwordCallbackName = passwordCallbackName; + } + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (handleCallback(callback)) { + continue; + } else if (callback instanceof NameCallback) { + ((NameCallback) callback).setName(username); + } else if (callback instanceof PasswordCallback) { + PasswordCallback pwCallback = (PasswordCallback) callback; + pwCallback.setPassword(password != null ? password.toCharArray() : null); + } else if (!invokePasswordCallback(callback)) { + String errorMsg = "Unsupported callback type " + callback.getClass().getName(); + log.info(errorMsg); + throw new UnsupportedCallbackException(callback, errorMsg); + } + } + } + + protected boolean handleCallback(Callback callback) { + return false; + } + + /* + * This method is called from the handle(Callback[]) method when the specified callback + * did not match any of the known callback classes. It looks for the callback method + * having the specified method name with one of the supported parameter types. + * If found, it invokes the callback method on the object and returns true. + * If not, it returns false. + */ + private boolean invokePasswordCallback(Callback callback) { + String cbname = passwordCallbackName == null ? PASSWORD_CALLBACK_NAME : passwordCallbackName; + for (Class arg : PASSWORD_CALLBACK_TYPES) { + try { + Method method = callback.getClass().getMethod(cbname, arg); + Object[] args = { + arg == String.class ? password : password != null ? password.toCharArray() : null + }; + method.invoke(callback, args); + return true; + } catch (Exception e) { + // ignore and continue + log.debug(e.toString()); + } + } + return false; + } +} diff --git a/client/src/main/java/org/asynchttpclient/spnego/SpnegoEngine.java b/client/src/main/java/org/asynchttpclient/spnego/SpnegoEngine.java new file mode 100644 index 0000000000..d67d923bb7 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/spnego/SpnegoEngine.java @@ -0,0 +1,297 @@ +/* + * 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. + */ +/* + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.asynchttpclient.spnego; + +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import java.io.IOException; +import java.net.InetAddress; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +/** + * SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication scheme. + * + * @since 4.1 + */ +public class SpnegoEngine { + + private static final String SPNEGO_OID = "1.3.6.1.5.5.2"; + private static final String KERBEROS_OID = "1.2.840.113554.1.2.2"; + private static final Map instances = new HashMap<>(); + private final Logger log = LoggerFactory.getLogger(getClass()); + private final @Nullable SpnegoTokenGenerator spnegoGenerator; + private final @Nullable String username; + private final @Nullable String password; + private final @Nullable String servicePrincipalName; + private final @Nullable String realmName; + private final boolean useCanonicalHostname; + private final @Nullable String loginContextName; + private final @Nullable Map customLoginConfig; + + public SpnegoEngine(final @Nullable String username, final @Nullable String password, + final @Nullable String servicePrincipalName, final @Nullable String realmName, + final boolean useCanonicalHostname, final @Nullable Map customLoginConfig, + final @Nullable String loginContextName, final @Nullable SpnegoTokenGenerator spnegoGenerator) { + this.username = username; + this.password = password; + this.servicePrincipalName = servicePrincipalName; + this.realmName = realmName; + this.useCanonicalHostname = useCanonicalHostname; + this.customLoginConfig = customLoginConfig; + this.spnegoGenerator = spnegoGenerator; + this.loginContextName = loginContextName; + } + + public SpnegoEngine() { + this(null, null, null, null, true, null, null, null); + } + + public static SpnegoEngine instance(final @Nullable String username, final @Nullable String password, + final @Nullable String servicePrincipalName, final @Nullable String realmName, + final boolean useCanonicalHostname, final @Nullable Map customLoginConfig, + final @Nullable String loginContextName) { + String key = ""; + if (customLoginConfig != null && !customLoginConfig.isEmpty()) { + StringBuilder customLoginConfigKeyValues = new StringBuilder(); + for (Map.Entry entry : customLoginConfig.entrySet()) { + customLoginConfigKeyValues + .append(entry.getKey()) + .append('=') + .append(entry.getValue()); + } + key = customLoginConfigKeyValues.toString(); + } + + if (username != null) { + key += username; + } + + if (loginContextName != null) { + key += loginContextName; + } + + if (!instances.containsKey(key)) { + instances.put(key, new SpnegoEngine(username, + password, + servicePrincipalName, + realmName, + useCanonicalHostname, + customLoginConfig, + loginContextName, + null)); + } + return instances.get(key); + } + + public String generateToken(String host) throws SpnegoEngineException { + GSSContext gssContext = null; + byte[] token = null; // base64 decoded challenge + Oid negotiationOid; + + try { + /* + * Using the SPNEGO OID is the correct method. Kerberos v5 works for IIS but not JBoss. + * Unwrapping the initial token when using SPNEGO OID looks like what is described here... + * + * http://msdn.microsoft.com/en-us/library/ms995330.aspx + * + * Another helpful URL... + * + * http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/tsec_SPNEGO_token.html + * + * Unfortunately SPNEGO is JRE >=1.6. + */ + // Try SPNEGO by default, fall back to Kerberos later if error + negotiationOid = new Oid(SPNEGO_OID); + + String spn = getCompleteServicePrincipalName(host); + try { + GSSManager manager = GSSManager.getInstance(); + GSSName serverName = manager.createName(spn, GSSName.NT_HOSTBASED_SERVICE); + GSSCredential myCred = null; + if (username != null || loginContextName != null || customLoginConfig != null && !customLoginConfig.isEmpty()) { + String contextName = loginContextName; + if (contextName == null) { + contextName = ""; + } + LoginContext loginContext = new LoginContext(contextName, null, getUsernamePasswordHandler(), getLoginConfiguration()); + loginContext.login(); + final Oid negotiationOidFinal = negotiationOid; + final PrivilegedExceptionAction action = () -> + manager.createCredential(null, GSSCredential.INDEFINITE_LIFETIME, negotiationOidFinal, GSSCredential.INITIATE_AND_ACCEPT); + myCred = Subject.doAs(loginContext.getSubject(), action); + } + gssContext = manager.createContext(useCanonicalHostname ? serverName.canonicalize(negotiationOid) : serverName, + negotiationOid, + myCred, + GSSContext.DEFAULT_LIFETIME); + gssContext.requestMutualAuth(true); + gssContext.requestCredDeleg(true); + } catch (GSSException ex) { + log.error("generateToken", ex); + // BAD MECH means we are likely to be using 1.5, fall back to Kerberos MECH. + // Rethrow any other exception. + if (ex.getMajor() == GSSException.BAD_MECH) { + log.debug("GSSException BAD_MECH, retry with Kerberos MECH"); + } else { + throw ex; + } + + } + if (gssContext == null) { + /* Kerberos v5 GSS-API mechanism defined in RFC 1964. */ + log.debug("Using Kerberos MECH {}", KERBEROS_OID); + negotiationOid = new Oid(KERBEROS_OID); + GSSManager manager = GSSManager.getInstance(); + GSSName serverName = manager.createName(spn, GSSName.NT_HOSTBASED_SERVICE); + gssContext = manager.createContext(serverName.canonicalize(negotiationOid), negotiationOid, null, + GSSContext.DEFAULT_LIFETIME); + gssContext.requestMutualAuth(true); + gssContext.requestCredDeleg(true); + } + + // TODO suspicious: this will always be null because no value has been assigned before. Assign directly? + if (token == null) { + token = new byte[0]; + } + + token = gssContext.initSecContext(token, 0, token.length); + if (token == null) { + throw new SpnegoEngineException("GSS security context initialization failed"); + } + + /* + * IIS accepts Kerberos and SPNEGO tokens. Some other servers Jboss, Glassfish? seem to only accept SPNEGO. Below wraps Kerberos into SPNEGO token. + */ + if (spnegoGenerator != null && negotiationOid.toString().equals(KERBEROS_OID)) { + token = spnegoGenerator.generateSpnegoDERObject(token); + } + + gssContext.dispose(); + + String tokenstr = Base64.getEncoder().encodeToString(token); + log.debug("Sending response '{}' back to the server", tokenstr); + + return tokenstr; + } catch (GSSException gsse) { + log.error("generateToken", gsse); + if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) { + throw new SpnegoEngineException(gsse.getMessage(), gsse); + } + if (gsse.getMajor() == GSSException.NO_CRED) { + throw new SpnegoEngineException(gsse.getMessage(), gsse); + } + if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN || gsse.getMajor() == GSSException.DUPLICATE_TOKEN + || gsse.getMajor() == GSSException.OLD_TOKEN) { + throw new SpnegoEngineException(gsse.getMessage(), gsse); + } + // other error + throw new SpnegoEngineException(gsse.getMessage()); + } catch (IOException | LoginException | PrivilegedActionException ex) { + throw new SpnegoEngineException(ex.getMessage()); + } + } + + String getCompleteServicePrincipalName(String host) { + String name; + if (servicePrincipalName == null) { + if (useCanonicalHostname) { + host = getCanonicalHostname(host); + } + name = "HTTP@" + host; + } else { + name = servicePrincipalName; + if (realmName != null && !name.contains("@")) { + name += '@' + realmName; + } + } + log.debug("Service Principal Name is {}", name); + return name; + } + + private String getCanonicalHostname(String hostname) { + String canonicalHostname = hostname; + try { + InetAddress in = InetAddress.getByName(hostname); + canonicalHostname = in.getCanonicalHostName(); + log.debug("Resolved hostname={} to canonicalHostname={}", hostname, canonicalHostname); + } catch (Exception e) { + log.warn("Unable to resolve canonical hostname", e); + } + return canonicalHostname; + } + + private @Nullable CallbackHandler getUsernamePasswordHandler() { + if (username == null) { + return null; + } + return new NamePasswordCallbackHandler(username, password); + } + + public @Nullable Configuration getLoginConfiguration() { + if (customLoginConfig != null && !customLoginConfig.isEmpty()) { + return new Configuration() { + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + return new AppConfigurationEntry[]{ + new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, + customLoginConfig)}; + } + }; + } + return null; + } +} diff --git a/client/src/main/java/org/asynchttpclient/spnego/SpnegoEngineException.java b/client/src/main/java/org/asynchttpclient/spnego/SpnegoEngineException.java new file mode 100644 index 0000000000..a28c0996a9 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/spnego/SpnegoEngineException.java @@ -0,0 +1,34 @@ +/* + * 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.spnego; + +import org.jetbrains.annotations.Nullable; + +/** + * Signals SPNEGO protocol failure. + */ +public class SpnegoEngineException extends Exception { + + private static final long serialVersionUID = -3123799505052881438L; + + public SpnegoEngineException(@Nullable String message) { + super(message); + } + + public SpnegoEngineException(@Nullable String message, Throwable cause) { + super(message, cause); + } +} diff --git a/api/src/main/java/org/asynchttpclient/spnego/SpnegoTokenGenerator.java b/client/src/main/java/org/asynchttpclient/spnego/SpnegoTokenGenerator.java similarity index 98% rename from api/src/main/java/org/asynchttpclient/spnego/SpnegoTokenGenerator.java rename to client/src/main/java/org/asynchttpclient/spnego/SpnegoTokenGenerator.java index ee0b9876c4..fa1acc480b 100644 --- a/api/src/main/java/org/asynchttpclient/spnego/SpnegoTokenGenerator.java +++ b/client/src/main/java/org/asynchttpclient/spnego/SpnegoTokenGenerator.java @@ -35,7 +35,6 @@ * . * */ - package org.asynchttpclient.spnego; import java.io.IOException; @@ -43,11 +42,12 @@ /** * Abstract SPNEGO token generator. Implementations should take an Kerberos ticket and transform * into a SPNEGO token. - *

+ *
* Implementations of this interface are expected to be thread-safe. * * @since 4.1 */ +@FunctionalInterface public interface SpnegoTokenGenerator { byte[] generateSpnegoDERObject(byte[] kerberosTicket) throws IOException; diff --git a/client/src/main/java/org/asynchttpclient/uri/Uri.java b/client/src/main/java/org/asynchttpclient/uri/Uri.java new file mode 100644 index 0000000000..e1d53d1cad --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/uri/Uri.java @@ -0,0 +1,286 @@ +/* + * 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. + * 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.uri; + +import org.asynchttpclient.util.StringBuilderPool; +import org.jetbrains.annotations.Nullable; + +import java.net.URI; +import java.net.URISyntaxException; + +import static org.asynchttpclient.util.Assertions.assertNotEmpty; +import static org.asynchttpclient.util.MiscUtils.isEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; + +public class Uri { + + public static final String HTTP = "http"; + public static final String HTTPS = "https"; + public static final String WS = "ws"; + public static final String WSS = "wss"; + private final String scheme; + private final @Nullable String userInfo; + private final String host; + private final int port; + private final @Nullable String query; + private final String path; + private final @Nullable String fragment; + private @Nullable String url; + private final boolean secured; + private final boolean webSocket; + + public Uri(String scheme, @Nullable String userInfo, String host, int port, String path, @Nullable String query, @Nullable String fragment) { + this.scheme = assertNotEmpty(scheme, "scheme"); + this.userInfo = userInfo; + this.host = assertNotEmpty(host, "host"); + this.port = port; + this.path = path; + this.query = query; + this.fragment = fragment; + secured = HTTPS.equals(scheme) || WSS.equals(scheme); + webSocket = WS.equals(scheme) || WSS.equalsIgnoreCase(scheme); + } + + public static Uri create(String originalUrl) { + return create(null, originalUrl); + } + + public static Uri create(@Nullable Uri context, final String originalUrl) { + UriParser parser = UriParser.parse(context, originalUrl); + if (isEmpty(parser.scheme)) { + throw new IllegalArgumentException(originalUrl + " could not be parsed into a proper Uri, missing scheme"); + } + if (isEmpty(parser.host)) { + throw new IllegalArgumentException(originalUrl + " could not be parsed into a proper Uri, missing host"); + } + + return new Uri(parser.scheme, parser.userInfo, parser.host, parser.port, parser.path, parser.query, parser.fragment); + } + + public @Nullable String getQuery() { + return query; + } + + public String getPath() { + return path; + } + + public @Nullable String getUserInfo() { + return userInfo; + } + + public int getPort() { + return port; + } + + public String getScheme() { + return scheme; + } + + public String getHost() { + return host; + } + + public @Nullable String getFragment() { + return fragment; + } + + public boolean isSecured() { + return secured; + } + + public boolean isWebSocket() { + return webSocket; + } + + public URI toJavaNetURI() throws URISyntaxException { + return new URI(toUrl()); + } + + public int getExplicitPort() { + return port == -1 ? getSchemeDefaultPort() : port; + } + + public int getSchemeDefaultPort() { + return isSecured() ? 443 : 80; + } + + public String toUrl() { + if (url == null) { + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); + sb.append(scheme).append("://"); + if (userInfo != null) { + sb.append(userInfo).append('@'); + } + sb.append(host); + if (port != -1) { + sb.append(':').append(port); + } + if (path != null) { + sb.append(path); + } + if (query != null) { + sb.append('?').append(query); + } + url = sb.toString(); + sb.setLength(0); + } + return url; + } + + /** + * @return [scheme]://[hostname](:[port])/path. Port is omitted if it matches the scheme's default one. + */ + public String toBaseUrl() { + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); + sb.append(scheme).append("://").append(host); + if (port != -1 && port != getSchemeDefaultPort()) { + sb.append(':').append(port); + } + if (isNonEmpty(path)) { + sb.append(path); + } + return sb.toString(); + } + + public String toRelativeUrl() { + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); + if (isNonEmpty(path)) { + sb.append(path); + } else { + sb.append('/'); + } + if (query != null) { + sb.append('?').append(query); + } + + return sb.toString(); + } + + public String toFullUrl() { + return fragment == null ? toUrl() : toUrl() + '#' + fragment; + } + + public String getBaseUrl() { + return scheme + "://" + host + ':' + getExplicitPort(); + } + + public String getAuthority() { + return host + ':' + getExplicitPort(); + } + + public boolean isSameBase(Uri other) { + return scheme.equals(other.getScheme()) + && host.equals(other.getHost()) + && getExplicitPort() == other.getExplicitPort(); + } + + public String getNonEmptyPath() { + return isNonEmpty(path) ? path : "/"; + } + + @Override + public String toString() { + // for now, but might change + return toUrl(); + } + + public Uri withNewScheme(String newScheme) { + return new Uri(newScheme, userInfo, host, port, path, query, fragment); + } + + public Uri withNewQuery(@Nullable String newQuery) { + return new Uri(scheme, userInfo, host, port, path, newQuery, fragment); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (host == null ? 0 : host.hashCode()); + result = prime * result + (path == null ? 0 : path.hashCode()); + result = prime * result + port; + result = prime * result + (query == null ? 0 : query.hashCode()); + result = prime * result + (scheme == null ? 0 : scheme.hashCode()); + result = prime * result + (userInfo == null ? 0 : userInfo.hashCode()); + result = prime * result + (fragment == null ? 0 : fragment.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Uri other = (Uri) obj; + if (host == null) { + if (other.host != null) { + return false; + } + } else if (!host.equals(other.host)) { + return false; + } + if (path == null) { + if (other.path != null) { + return false; + } + } else if (!path.equals(other.path)) { + return false; + } + if (port != other.port) { + return false; + } + if (query == null) { + if (other.query != null) { + return false; + } + } else if (!query.equals(other.query)) { + return false; + } + if (scheme == null) { + if (other.scheme != null) { + return false; + } + } else if (!scheme.equals(other.scheme)) { + return false; + } + if (userInfo == null) { + if (other.userInfo != null) { + return false; + } + } else if (!userInfo.equals(other.userInfo)) { + return false; + } + if (fragment == null) { + return other.fragment == null; + } else { + return fragment.equals(other.fragment); + } + } + + public static void validateSupportedScheme(Uri uri) { + final String scheme = uri.getScheme(); + if (scheme == null || !scheme.equalsIgnoreCase(HTTP) && !scheme.equalsIgnoreCase(HTTPS) && !scheme.equalsIgnoreCase(WS) && !scheme.equalsIgnoreCase(WSS)) { + throw new IllegalArgumentException("The URI scheme, of the URI " + uri + ", must be equal (ignoring case) to 'http', 'https', 'ws', or 'wss'"); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/uri/UriParser.java b/client/src/main/java/org/asynchttpclient/uri/UriParser.java new file mode 100644 index 0000000000..c65f145dd2 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/uri/UriParser.java @@ -0,0 +1,377 @@ +/* + * 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. + * 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.uri; + +import org.jetbrains.annotations.Nullable; + +import static java.util.Objects.requireNonNull; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; + +final class UriParser { + + public @Nullable String scheme; + public @Nullable String host; + public int port = -1; + public @Nullable String query; + public @Nullable String fragment; + private @Nullable String authority; + public String path = ""; + public @Nullable String userInfo; + + private final String originalUrl; + private int start, end, currentIndex; + + private UriParser(final String originalUrl) { + this.originalUrl = originalUrl; + } + + private void trimLeft() { + while (start < end && originalUrl.charAt(start) <= ' ') { + start++; + } + + if (originalUrl.regionMatches(true, start, "url:", 0, 4)) { + start += 4; + } + } + + private void trimRight() { + end = originalUrl.length(); + while (end > 0 && originalUrl.charAt(end - 1) <= ' ') { + end--; + } + } + + private boolean isFragmentOnly() { + return start < originalUrl.length() && originalUrl.charAt(start) == '#'; + } + + private static boolean isValidProtocolChar(char c) { + return Character.isLetterOrDigit(c) && c != '.' && c != '+' && c != '-'; + } + + private static boolean isValidProtocolChars(String protocol) { + for (int i = 1; i < protocol.length(); i++) { + if (!isValidProtocolChar(protocol.charAt(i))) { + return false; + } + } + return true; + } + + private static boolean isValidProtocol(String protocol) { + return protocol.length() > 0 && Character.isLetter(protocol.charAt(0)) && isValidProtocolChars(protocol); + } + + private void computeInitialScheme() { + for (int i = currentIndex; i < end; i++) { + char c = originalUrl.charAt(i); + if (c == ':') { + String s = originalUrl.substring(currentIndex, i); + if (isValidProtocol(s)) { + scheme = s.toLowerCase(); + currentIndex = i + 1; + } + break; + } else if (c == '/') { + break; + } + } + } + + private boolean overrideWithContext(@Nullable Uri context) { + + boolean isRelative = false; + + // use context only if schemes match + if (context != null && (scheme == null || scheme.equalsIgnoreCase(context.getScheme()))) { + + // see RFC2396 5.2.3 + String contextPath = context.getPath(); + if (isNonEmpty(contextPath) && contextPath.charAt(0) == '/') { + scheme = null; + } + + if (scheme == null) { + scheme = context.getScheme(); + userInfo = context.getUserInfo(); + host = context.getHost(); + port = context.getPort(); + path = contextPath; + isRelative = true; + } + } + return isRelative; + } + + private int findWithinCurrentRange(char c) { + int pos = originalUrl.indexOf(c, currentIndex); + return pos > end ? -1 : pos; + } + + private void trimFragment() { + int charpPosition = findWithinCurrentRange('#'); + if (charpPosition >= 0) { + end = charpPosition; + if (charpPosition + 1 < originalUrl.length()) { + fragment = originalUrl.substring(charpPosition + 1); + } + } + } + + // isRelative can be true only when context is not null + @SuppressWarnings("NullAway") + private void inheritContextQuery(@Nullable Uri context, boolean isRelative) { + // see RFC2396 5.2.2: query and fragment inheritance + if (isRelative && currentIndex == end) { + query = context.getQuery(); + fragment = context.getFragment(); + } + } + + private boolean computeQuery() { + if (currentIndex < end) { + int askPosition = findWithinCurrentRange('?'); + if (askPosition != -1) { + query = originalUrl.substring(askPosition + 1, end); + if (end > askPosition) { + end = askPosition; + } + return askPosition == currentIndex; + } + } + return false; + } + + private boolean currentPositionStartsWith4Slashes() { + return originalUrl.regionMatches(currentIndex, "////", 0, 4); + } + + private boolean currentPositionStartsWith2Slashes() { + return originalUrl.regionMatches(currentIndex, "//", 0, 2); + } + + private String computeAuthority() { + int authorityEndPosition = findWithinCurrentRange('/'); + if (authorityEndPosition == -1) { + authorityEndPosition = findWithinCurrentRange('?'); + if (authorityEndPosition == -1) { + authorityEndPosition = end; + } + } + host = authority = originalUrl.substring(currentIndex, authorityEndPosition); + currentIndex = authorityEndPosition; + return authority; + } + + private void computeUserInfo(String nonNullAuthority) { + int atPosition = nonNullAuthority.indexOf('@'); + if (atPosition != -1) { + userInfo = nonNullAuthority.substring(0, atPosition); + host = nonNullAuthority.substring(atPosition + 1); + } else { + userInfo = null; + } + } + + 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) == '['; + } + + private void computeIPV6(String nonNullHost) { + int positionAfterClosingSquareBrace = nonNullHost.indexOf(']') + 1; + if (positionAfterClosingSquareBrace > 1) { + + port = -1; + + if (nonNullHost.length() > positionAfterClosingSquareBrace) { + if (nonNullHost.charAt(positionAfterClosingSquareBrace) == ':') { + // see RFC2396: port can be null + int portPosition = positionAfterClosingSquareBrace + 1; + if (nonNullHost.length() > portPosition) { + port = Integer.parseInt(nonNullHost.substring(portPosition)); + } + } else { + throw new IllegalArgumentException("Invalid authority field: " + authority); + } + } + + host = nonNullHost.substring(0, positionAfterClosingSquareBrace); + + } else { + throw new IllegalArgumentException("Invalid authority field: " + authority); + } + } + + private void computeRegularHostPort(String nonNullHost) { + int colonPosition = nonNullHost.indexOf(':'); + port = -1; + if (colonPosition >= 0) { + // see RFC2396: port can be null + int portPosition = colonPosition + 1; + if (nonNullHost.length() > portPosition) { + port = Integer.parseInt(nonNullHost.substring(portPosition)); + } + host = nonNullHost.substring(0, colonPosition); + } + } + + // /./ + private void removeEmbeddedDot() { + path = path.replace("/./", "/"); + } + + // /../ + private void removeEmbedded2Dots() { + int i = 0; + while ((i = path.indexOf("/../", i)) >= 0) { + if (i > 0) { + end = path.lastIndexOf('/', i - 1); + if (end >= 0 && path.indexOf("/../", end) != 0) { + path = path.substring(0, end) + path.substring(i + 3); + i = 0; + } else if (end == 0) { + break; + } + } else { + i += 3; + } + } + } + + private void removeTailing2Dots() { + while (path.endsWith("/..")) { + end = path.lastIndexOf('/', path.length() - 4); + if (end >= 0) { + path = path.substring(0, end + 1); + } else { + break; + } + } + } + + private void removeStartingDot() { + if (path.startsWith("./") && path.length() > 2) { + path = path.substring(2); + } + } + + private void removeTrailingDot() { + if (path.endsWith("/.")) { + path = path.substring(0, path.length() - 1); + } + } + + private void handleRelativePath() { + int lastSlashPosition = path.lastIndexOf('/'); + String pathEnd = originalUrl.substring(currentIndex, end); + + if (lastSlashPosition == -1) { + path = authority != null ? '/' + pathEnd : pathEnd; + } else { + path = path.substring(0, lastSlashPosition + 1) + pathEnd; + } + } + + private void handlePathDots() { + if (path.indexOf('.') != -1) { + removeEmbeddedDot(); + removeEmbedded2Dots(); + removeTailing2Dots(); + removeStartingDot(); + removeTrailingDot(); + } + } + + private void parseAuthority() { + if (!currentPositionStartsWith4Slashes() && currentPositionStartsWith2Slashes()) { + currentIndex += 2; + + String nonNullAuthority = computeAuthority(); + computeUserInfo(nonNullAuthority); + + if (host != null) { + String nonNullHost = host; + if (isMaybeIPV6(nonNullHost)) { + computeIPV6(nonNullHost); + } else { + computeRegularHostPort(nonNullHost); + } + } + + if (port < -1) { + throw new IllegalArgumentException("Invalid port number :" + port); + } + + // see RFC2396 5.2.4: ignore context path if authority is defined + if (isNonEmpty(authority)) { + path = ""; + } + } + } + + private void computeRegularPath() { + if (originalUrl.charAt(currentIndex) == '/') { + path = originalUrl.substring(currentIndex, end); + } else if (isNonEmpty(path)) { + handleRelativePath(); + } else { + String pathEnd = originalUrl.substring(currentIndex, end); + path = isNonEmpty(pathEnd) && pathEnd.charAt(0) != '/' ? '/' + pathEnd : pathEnd; + } + handlePathDots(); + } + + private void computeQueryOnlyPath() { + int lastSlashPosition = path.lastIndexOf('/'); + path = lastSlashPosition < 0 ? "/" : path.substring(0, lastSlashPosition) + '/'; + } + + private void computePath(boolean queryOnly) { + // Parse the file path if any + if (currentIndex < end) { + computeRegularPath(); + } else if (queryOnly) { + computeQueryOnlyPath(); + } + } + + private void parse(@Nullable Uri context) { + end = originalUrl.length(); + + trimLeft(); + trimRight(); + currentIndex = start; + if (!isFragmentOnly()) { + computeInitialScheme(); + } + boolean isRelative = overrideWithContext(context); + trimFragment(); + inheritContextQuery(context, isRelative); + boolean queryOnly = computeQuery(); + parseAuthority(); + computePath(queryOnly); + } + + public static UriParser parse(@Nullable Uri context, final String originalUrl) { + requireNonNull(originalUrl, "originalUrl"); + final UriParser parser = new UriParser(originalUrl); + parser.parse(context); + return parser; + } +} \ No newline at end of file diff --git a/client/src/main/java/org/asynchttpclient/util/Assertions.java b/client/src/main/java/org/asynchttpclient/util/Assertions.java new file mode 100644 index 0000000000..0b2e38a7c1 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/Assertions.java @@ -0,0 +1,36 @@ +/* + * 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.util; + +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 String assertNotEmpty(@Nullable String value, String name) { + requireNonNull(value, name); + if (value.length() == 0) { + throw new IllegalArgumentException("empty " + name); + } + return value; + } +} diff --git a/client/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java b/client/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java new file mode 100644 index 0000000000..4e2c4aed3b --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2010-2013 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.util; + +import org.asynchttpclient.Realm; +import org.asynchttpclient.Request; +import org.asynchttpclient.ntlm.NtlmEngine; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.spnego.SpnegoEngine; +import org.asynchttpclient.spnego.SpnegoEngineException; +import org.asynchttpclient.uri.Uri; +import org.jetbrains.annotations.Nullable; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; + +import static io.netty.handler.codec.http.HttpHeaderNames.PROXY_AUTHORIZATION; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static org.asynchttpclient.Dsl.realm; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; + +public final class AuthenticatorUtils { + + public static final String NEGOTIATE = "Negotiate"; + + private AuthenticatorUtils() { + // Prevent outside initialization + } + + public static @Nullable String getHeaderWithPrefix(@Nullable List authenticateHeaders, String prefix) { + if (authenticateHeaders != null) { + for (String authenticateHeader : authenticateHeaders) { + if (authenticateHeader.regionMatches(true, 0, prefix, 0, prefix.length())) { + return authenticateHeader; + } + } + } + + return null; + } + + private static @Nullable String computeBasicAuthentication(@Nullable Realm realm) { + return realm != null ? computeBasicAuthentication(realm.getPrincipal(), realm.getPassword(), realm.getCharset()) : null; + } + + private static String computeBasicAuthentication(@Nullable String principal, @Nullable String password, Charset charset) { + String s = principal + ':' + password; + return "Basic " + Base64.getEncoder().encodeToString(s.getBytes(charset)); + } + + public static String computeRealmURI(Uri uri, boolean useAbsoluteURI, boolean omitQuery) { + if (useAbsoluteURI) { + return omitQuery && isNonEmpty(uri.getQuery()) ? uri.withNewQuery(null).toUrl() : uri.toUrl(); + } else { + String path = uri.getNonEmptyPath(); + return omitQuery || !isNonEmpty(uri.getQuery()) ? path : path + '?' + uri.getQuery(); + } + } + + private static String computeDigestAuthentication(Realm realm, Uri uri) { + + String realmUri = computeRealmURI(uri, realm.isUseAbsoluteURI(), realm.isOmitQuery()); + + StringBuilder builder = new StringBuilder().append("Digest "); + append(builder, "username", realm.getPrincipal(), true); + append(builder, "realm", realm.getRealmName(), true); + append(builder, "nonce", realm.getNonce(), true); + append(builder, "uri", realmUri, true); + if (isNonEmpty(realm.getAlgorithm())) { + append(builder, "algorithm", realm.getAlgorithm(), false); + } + + append(builder, "response", realm.getResponse(), true); + + if (realm.getOpaque() != null) { + append(builder, "opaque", realm.getOpaque(), true); + } + + if (realm.getQop() != null) { + append(builder, "qop", realm.getQop(), false); + // nc and cnonce only sent if server sent qop + append(builder, "nc", realm.getNc(), false); + append(builder, "cnonce", realm.getCnonce(), true); + } + builder.setLength(builder.length() - 2); // remove tailing ", " + + // FIXME isn't there a more efficient way? + return new String(StringUtils.charSequence2Bytes(builder, ISO_8859_1), StandardCharsets.UTF_8); + } + + private static void append(StringBuilder builder, String name, @Nullable String value, boolean quoted) { + builder.append(name).append('='); + if (quoted) { + builder.append('"').append(value).append('"'); + } else { + builder.append(value); + } + builder.append(", "); + } + + public static @Nullable String perConnectionProxyAuthorizationHeader(Request request, @Nullable Realm proxyRealm) { + String proxyAuthorization = null; + if (proxyRealm != null && proxyRealm.isUsePreemptiveAuth()) { + switch (proxyRealm.getScheme()) { + case NTLM: + case KERBEROS: + case SPNEGO: + List auth = request.getHeaders().getAll(PROXY_AUTHORIZATION); + if (getHeaderWithPrefix(auth, "NTLM") == null) { + String msg = NtlmEngine.INSTANCE.generateType1Msg(); + proxyAuthorization = "NTLM " + msg; + } + + break; + default: + } + } + + return proxyAuthorization; + } + + public static @Nullable String perRequestProxyAuthorizationHeader(Request request, @Nullable Realm proxyRealm) { + String proxyAuthorization = null; + if (proxyRealm != null && proxyRealm.isUsePreemptiveAuth()) { + + switch (proxyRealm.getScheme()) { + case BASIC: + proxyAuthorization = computeBasicAuthentication(proxyRealm); + break; + case DIGEST: + if (isNonEmpty(proxyRealm.getNonce())) { + // update realm with request information + final Uri uri = request.getUri(); + proxyRealm = realm(proxyRealm) + .setUri(uri) + .setMethodName(request.getMethod()) + .build(); + proxyAuthorization = computeDigestAuthentication(proxyRealm, uri); + } + break; + case NTLM: + case KERBEROS: + case SPNEGO: + // NTLM, KERBEROS and SPNEGO are only set on the first request with a connection, + // see perConnectionProxyAuthorizationHeader + break; + default: + throw new IllegalStateException("Invalid Authentication scheme " + proxyRealm.getScheme()); + } + } + + return proxyAuthorization; + } + + public static @Nullable String perConnectionAuthorizationHeader(Request request, @Nullable ProxyServer proxyServer, + @Nullable Realm realm) { + String authorizationHeader = null; + + if (realm != null && realm.isUsePreemptiveAuth()) { + switch (realm.getScheme()) { + case NTLM: + String msg = NtlmEngine.INSTANCE.generateType1Msg(); + authorizationHeader = "NTLM " + msg; + break; + case KERBEROS: + case SPNEGO: + String host; + if (proxyServer != null) { + host = proxyServer.getHost(); + } else if (request.getVirtualHost() != null) { + host = request.getVirtualHost(); + } else { + host = request.getUri().getHost(); + } + + try { + authorizationHeader = NEGOTIATE + ' ' + SpnegoEngine.instance( + realm.getPrincipal(), + realm.getPassword(), + realm.getServicePrincipalName(), + realm.getRealmName(), + realm.isUseCanonicalHostname(), + realm.getCustomLoginConfig(), + realm.getLoginContextName()).generateToken(host); + } catch (SpnegoEngineException e) { + throw new RuntimeException(e); + } + break; + default: + break; + } + } + + return authorizationHeader; + } + + public static @Nullable String perRequestAuthorizationHeader(Request request, @Nullable Realm realm) { + String authorizationHeader = null; + if (realm != null && realm.isUsePreemptiveAuth()) { + + switch (realm.getScheme()) { + case BASIC: + authorizationHeader = computeBasicAuthentication(realm); + break; + case DIGEST: + if (isNonEmpty(realm.getNonce())) { + // update realm with request information + final Uri uri = request.getUri(); + realm = realm(realm) + .setUri(uri) + .setMethodName(request.getMethod()) + .build(); + authorizationHeader = computeDigestAuthentication(realm, uri); + } + break; + case NTLM: + case KERBEROS: + case SPNEGO: + // NTLM, KERBEROS and SPNEGO are only set on the first request with a connection, + // see perConnectionAuthorizationHeader + break; + default: + throw new IllegalStateException("Invalid Authentication " + realm); + } + } + + return authorizationHeader; + } +} diff --git a/client/src/main/java/org/asynchttpclient/util/Counted.java b/client/src/main/java/org/asynchttpclient/util/Counted.java new file mode 100644 index 0000000000..f861d01d16 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/Counted.java @@ -0,0 +1,40 @@ +/* + * 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.util; + +import org.asynchttpclient.AsyncHttpClient; + +/** + * An interface that defines useful methods to check how many {@linkplain AsyncHttpClient} + * instances this particular implementation is shared with. + */ +public interface Counted { + + /** + * Increment counter and return the incremented value + */ + int incrementAndGet(); + + /** + * Decrement counter and return the decremented value + */ + int decrementAndGet(); + + /** + * Return the current counter + */ + int count(); +} diff --git a/client/src/main/java/org/asynchttpclient/util/DateUtils.java b/client/src/main/java/org/asynchttpclient/util/DateUtils.java new file mode 100644 index 0000000000..39199dea69 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/DateUtils.java @@ -0,0 +1,27 @@ +/* + * 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.util; + +public final class DateUtils { + + private DateUtils() { + // Prevent outside initialization + } + + public static long unpreciseMillisTime() { + return System.currentTimeMillis(); + } +} diff --git a/client/src/main/java/org/asynchttpclient/util/EnsuresNonNull.java b/client/src/main/java/org/asynchttpclient/util/EnsuresNonNull.java new file mode 100644 index 0000000000..64ccee659e --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/EnsuresNonNull.java @@ -0,0 +1,17 @@ +package org.asynchttpclient.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * NullAway supports @EnsuresNonNull(param) annotation, but org.jetbrains.annotations doesn't include this annotation. + * The purpose of this annotation is to tell NullAway that if the annotated method succeeded without any exceptions, + * all class's fields defined in "param" will be @NotNull. + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.METHOD) +public @interface EnsuresNonNull { + String[] value(); +} diff --git a/client/src/main/java/org/asynchttpclient/util/HttpConstants.java b/client/src/main/java/org/asynchttpclient/util/HttpConstants.java new file mode 100644 index 0000000000..4a1a128650 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/HttpConstants.java @@ -0,0 +1,59 @@ +/* + * 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.util; + +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpResponseStatus; + +public final class HttpConstants { + + private HttpConstants() { + // Prevent outside initialization + } + + public static final class Methods { + public static final String CONNECT = HttpMethod.CONNECT.name(); + public static final String DELETE = HttpMethod.DELETE.name(); + public static final String GET = HttpMethod.GET.name(); + public static final String HEAD = HttpMethod.HEAD.name(); + public static final String OPTIONS = HttpMethod.OPTIONS.name(); + public static final String PATCH = HttpMethod.PATCH.name(); + public static final String POST = HttpMethod.POST.name(); + public static final String PUT = HttpMethod.PUT.name(); + public static final String TRACE = HttpMethod.TRACE.name(); + + private Methods() { + // Prevent outside initialization + } + } + + public static final class ResponseStatusCodes { + public static final int CONTINUE_100 = HttpResponseStatus.CONTINUE.code(); + public static final int SWITCHING_PROTOCOLS_101 = HttpResponseStatus.SWITCHING_PROTOCOLS.code(); + public static final int OK_200 = HttpResponseStatus.OK.code(); + public static final int MOVED_PERMANENTLY_301 = HttpResponseStatus.MOVED_PERMANENTLY.code(); + public static final int FOUND_302 = HttpResponseStatus.FOUND.code(); + public static final int SEE_OTHER_303 = HttpResponseStatus.SEE_OTHER.code(); + public static final int TEMPORARY_REDIRECT_307 = HttpResponseStatus.TEMPORARY_REDIRECT.code(); + public static final int PERMANENT_REDIRECT_308 = HttpResponseStatus.PERMANENT_REDIRECT.code(); + public static final int UNAUTHORIZED_401 = HttpResponseStatus.UNAUTHORIZED.code(); + public static final int PROXY_AUTHENTICATION_REQUIRED_407 = HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED.code(); + + private ResponseStatusCodes() { + // Prevent outside initialization + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/util/HttpUtils.java b/client/src/main/java/org/asynchttpclient/util/HttpUtils.java new file mode 100644 index 0000000000..3cca41e616 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/HttpUtils.java @@ -0,0 +1,185 @@ +/* + * 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.util; + +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.util.AsciiString; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.Param; +import org.asynchttpclient.Request; +import org.asynchttpclient.uri.Uri; +import org.jetbrains.annotations.Nullable; + +import java.net.URLEncoder; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * {@link AsyncHttpClient} common utilities. + */ +public final class HttpUtils { + + public static final AsciiString ACCEPT_ALL_HEADER_VALUE = new AsciiString("*/*"); + public static final AsciiString GZIP_DEFLATE = new AsciiString(HttpHeaderValues.GZIP + "," + HttpHeaderValues.DEFLATE); + 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 + } + + public static String hostHeader(Uri uri) { + String host = uri.getHost(); + int port = uri.getPort(); + return port == -1 || port == uri.getSchemeDefaultPort() ? host : host + ':' + port; + } + + public static String originHeader(Uri uri) { + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); + sb.append(uri.isSecured() ? "https://" : "http://").append(uri.getHost()); + if (uri.getExplicitPort() != uri.getSchemeDefaultPort()) { + sb.append(':').append(uri.getPort()); + } + return sb.toString(); + } + + public static @Nullable Charset extractContentTypeCharsetAttribute(String contentType) { + String charsetName = extractContentTypeAttribute(contentType, CONTENT_TYPE_CHARSET_ATTRIBUTE); + return charsetName != null ? Charset.forName(charsetName) : null; + } + + public static @Nullable String extractContentTypeBoundaryAttribute(String contentType) { + return extractContentTypeAttribute(contentType, CONTENT_TYPE_BOUNDARY_ATTRIBUTE); + } + + private static @Nullable String extractContentTypeAttribute(@Nullable String contentType, String attribute) { + if (contentType == null) { + return null; + } + + for (int i = 0; i < contentType.length(); i++) { + if (contentType.regionMatches(true, i, attribute, 0, + attribute.length())) { + int start = i + attribute.length(); + + // trim left + while (start < contentType.length()) { + char c = contentType.charAt(start); + if (c == ' ' || c == '\'' || c == '"') { + start++; + } else { + break; + } + } + if (start == contentType.length()) { + break; + } + + // trim right + int end = start + 1; + while (end < contentType.length()) { + char c = contentType.charAt(end); + if (c == ' ' || c == '\'' || c == '"' || c == ';') { + break; + } else { + end++; + } + } + + return contentType.substring(start, end); + } + } + + return null; + } + + // The pool of ASCII chars to be used for generating a multipart boundary. + private static final byte[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(US_ASCII); + + // a random size from 30 to 40 + public static byte[] computeMultipartBoundary() { + ThreadLocalRandom random = ThreadLocalRandom.current(); + byte[] bytes = new byte[random.nextInt(11) + 30]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = MULTIPART_CHARS[random.nextInt(MULTIPART_CHARS.length)]; + } + return bytes; + } + + public static String patchContentTypeWithBoundaryAttribute(String base, byte[] boundary) { + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder().append(base); + if (!base.isEmpty() && base.charAt(base.length() - 1) != ';') { + sb.append(';'); + } + return sb.append(' ').append(CONTENT_TYPE_BOUNDARY_ATTRIBUTE).append(new String(boundary, US_ASCII)).toString(); + } + + public static boolean followRedirect(AsyncHttpClientConfig config, Request request) { + return request.getFollowRedirect() != null ? request.getFollowRedirect() : config.isFollowRedirect(); + } + + public static ByteBuffer urlEncodeFormParams(List params, Charset charset) { + return StringUtils.charSequence2ByteBuffer(urlEncodeFormParams0(params, charset), US_ASCII); + } + + private static StringBuilder urlEncodeFormParams0(List params, Charset charset) { + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); + for (Param param : params) { + encodeAndAppendFormParam(sb, param.getName(), param.getValue(), charset); + } + sb.setLength(sb.length() - 1); + return sb; + } + + private static void encodeAndAppendFormParam(StringBuilder sb, String name, @Nullable String value, Charset charset) { + encodeAndAppendFormField(sb, name, charset); + if (value != null) { + sb.append('='); + encodeAndAppendFormField(sb, value, charset); + } + sb.append('&'); + } + + private static void encodeAndAppendFormField(StringBuilder sb, String field, Charset charset) { + if (charset.equals(UTF_8)) { + Utf8UrlEncoder.encodeAndAppendFormElement(sb, field); + } else { + // TODO there's probably room for perf improvements + sb.append(URLEncoder.encode(field, charset)); + } + } + + public static CharSequence filterOutBrotliFromAcceptEncoding(String acceptEncoding) { + // we don't support Brotly ATM + if (acceptEncoding.endsWith(BROTLY_ACCEPT_ENCODING_SUFFIX)) { + return acceptEncoding.subSequence(0, acceptEncoding.length() - BROTLY_ACCEPT_ENCODING_SUFFIX.length()); + } + 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/MessageDigestUtils.java b/client/src/main/java/org/asynchttpclient/util/MessageDigestUtils.java new file mode 100644 index 0000000000..c60e242380 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/MessageDigestUtils.java @@ -0,0 +1,54 @@ +/* + * 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.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public final class MessageDigestUtils { + + private static final ThreadLocal MD5_MESSAGE_DIGESTS = ThreadLocal.withInitial(() -> { + try { + return MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new InternalError("MD5 not supported on this platform"); + } + }); + + private static final ThreadLocal SHA1_MESSAGE_DIGESTS = ThreadLocal.withInitial(() -> { + try { + return MessageDigest.getInstance("SHA1"); + } catch (NoSuchAlgorithmException e) { + throw new InternalError("SHA1 not supported on this platform"); + } + }); + + private MessageDigestUtils() { + // Prevent outside initialization + } + + public static MessageDigest pooledMd5MessageDigest() { + MessageDigest md = MD5_MESSAGE_DIGESTS.get(); + md.reset(); + return md; + } + + public static MessageDigest pooledSha1MessageDigest() { + MessageDigest md = SHA1_MESSAGE_DIGESTS.get(); + md.reset(); + return md; + } +} diff --git a/client/src/main/java/org/asynchttpclient/util/MiscUtils.java b/client/src/main/java/org/asynchttpclient/util/MiscUtils.java new file mode 100644 index 0000000000..5a37cce759 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/MiscUtils.java @@ -0,0 +1,78 @@ +/* + * 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.util; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Collection; +import java.util.Map; + +public final class MiscUtils { + + private MiscUtils() { + // Prevent outside initialization + } + + // NullAway is not powerful enough to recognise that if the values has passed the check, it's not null + @Contract(value = "null -> false", pure = true) + public static boolean isNonEmpty(@Nullable String string) { + return !isEmpty(string); + } + + @Contract(value = "null -> true", pure = true) + public static boolean isEmpty(@Nullable String string) { + return string == null || string.isEmpty(); + } + + @Contract(value = "null -> false", pure = true) + public static boolean isNonEmpty(@Nullable Object[] array) { + return array != null && array.length != 0; + } + + @Contract(value = "null -> false", pure = true) + public static boolean isNonEmpty(byte @Nullable [] array) { + return array != null && array.length != 0; + } + + @Contract("null -> false") + public static boolean isNonEmpty(@Nullable Collection collection) { + return collection != null && !collection.isEmpty(); + } + + @Contract("null -> false") + public static boolean isNonEmpty(@Nullable Map map) { + return map != null && !map.isEmpty(); + } + + public static T withDefault(@Nullable T value, T def) { + return value == null ? def : value; + } + + public static void closeSilently(@Nullable Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + // + } + } + } + + public static Throwable getCause(Throwable t) { + Throwable cause = t.getCause(); + return cause != null ? getCause(cause) : t; + } +} diff --git a/client/src/main/java/org/asynchttpclient/util/ProxyUtils.java b/client/src/main/java/org/asynchttpclient/util/ProxyUtils.java new file mode 100644 index 0000000000..a7bf5b7e20 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/ProxyUtils.java @@ -0,0 +1,182 @@ +/* + * 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.util; + +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.Realm; +import org.asynchttpclient.Request; +import org.asynchttpclient.proxy.ProxyServer; +import org.asynchttpclient.proxy.ProxyServerSelector; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import static org.asynchttpclient.Dsl.basicAuthRealm; +import static org.asynchttpclient.Dsl.proxyServer; + +/** + * Utilities for Proxy handling. + * + * @author cstamas + */ +public final class ProxyUtils { + + /** + * The host to use as proxy. + * + * @see Networking Properties + */ + public static final String PROXY_HOST = "http.proxyHost"; + /** + * The port to use for the proxy. + * + * @see Networking Properties + */ + public static final String PROXY_PORT = "http.proxyPort"; + /** + * A specification of non-proxy hosts. + * + * @see Networking Properties + */ + public static final String PROXY_NONPROXYHOSTS = "http.nonProxyHosts"; + private static final Logger logger = LoggerFactory.getLogger(ProxyUtils.class); + private static final String PROPERTY_PREFIX = "org.asynchttpclient.AsyncHttpClientConfig.proxy."; + + /** + * The username to use for authentication for the proxy server. + */ + private static final String PROXY_USER = PROPERTY_PREFIX + "user"; + + /** + * The password to use for authentication for the proxy server. + */ + private static final String PROXY_PASSWORD = PROPERTY_PREFIX + "password"; + + private ProxyUtils() { + // Prevent outside initialization + } + + /** + * @param config the global config + * @param request the request + * @return the proxy server to be used for this request (can be null) + */ + public static @Nullable ProxyServer getProxyServer(AsyncHttpClientConfig config, Request request) { + ProxyServer proxyServer = request.getProxyServer(); + if (proxyServer == null) { + ProxyServerSelector selector = config.getProxyServerSelector(); + if (selector != null) { + proxyServer = selector.select(request.getUri()); + } + } + return proxyServer != null && !proxyServer.isIgnoredForHost(request.getUri().getHost()) ? proxyServer : null; + } + + /** + * Creates a proxy server instance from the given properties. + * 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. + * @see Networking Properties + * @see #PROXY_HOST + * @see #PROXY_PORT + * @see #PROXY_NONPROXYHOSTS + */ + public static ProxyServerSelector createProxyServerSelector(Properties properties) { + String host = properties.getProperty(PROXY_HOST); + + if (host != null) { + int port = Integer.valueOf(properties.getProperty(PROXY_PORT, "80")); + + String principal = properties.getProperty(PROXY_USER); + String password = properties.getProperty(PROXY_PASSWORD); + + Realm realm = null; + if (principal != null) { + realm = basicAuthRealm(principal, password).build(); + } + + ProxyServer.Builder proxyServer = proxyServer(host, port).setRealm(realm); + + String nonProxyHosts = properties.getProperty(PROXY_NONPROXYHOSTS); + if (nonProxyHosts != null) { + proxyServer.setNonProxyHosts(new ArrayList<>(Arrays.asList(nonProxyHosts.split("\\|")))); + } + + ProxyServer proxy = proxyServer.build(); + return uri -> proxy; + } + + return ProxyServerSelector.NO_PROXY_SELECTOR; + } + + /** + * Get a proxy server selector based on the JDK default proxy selector. + * + * @return The proxy server selector. + */ + public static ProxyServerSelector getJdkDefaultProxyServerSelector() { + return createProxyServerSelector(ProxySelector.getDefault()); + } + + /** + * Create a proxy server selector based on the passed in JDK proxy selector. + * + * @param proxySelector The proxy selector to use. Must not be null. + * @return The proxy server selector. + */ + private static ProxyServerSelector createProxyServerSelector(final ProxySelector proxySelector) { + return uri -> { + try { + URI javaUri = uri.toJavaNetURI(); + + List proxies = proxySelector.select(javaUri); + if (proxies != null) { + // Loop through them until we find one that we know how to use + for (Proxy proxy : proxies) { + switch (proxy.type()) { + case HTTP: + if (!(proxy.address() instanceof InetSocketAddress)) { + logger.warn("Don't know how to connect to address " + proxy.address()); + return null; + } else { + InetSocketAddress address = (InetSocketAddress) proxy.address(); + return proxyServer(address.getHostString(), address.getPort()).build(); + } + case DIRECT: + return null; + default: + logger.warn("ProxySelector returned proxy type that we don't know how to use: " + proxy.type()); + break; + } + } + } + return null; + } catch (URISyntaxException e) { + logger.warn(uri + " couldn't be turned into a java.net.URI", e); + return null; + } + }; + } +} diff --git a/client/src/main/java/org/asynchttpclient/util/StringBuilderPool.java b/client/src/main/java/org/asynchttpclient/util/StringBuilderPool.java new file mode 100644 index 0000000000..ae4f9c6766 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/StringBuilderPool.java @@ -0,0 +1,34 @@ +/* + * 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.util; + +public class StringBuilderPool { + + public static final StringBuilderPool DEFAULT = new StringBuilderPool(); + + private final ThreadLocal pool = ThreadLocal.withInitial(() -> new StringBuilder(512)); + + /** + * BEWARE: MUSTN'T APPEND TO ITSELF! + * + * @return a pooled StringBuilder + */ + public StringBuilder stringBuilder() { + StringBuilder sb = pool.get(); + sb.setLength(0); + return sb; + } +} diff --git a/client/src/main/java/org/asynchttpclient/util/StringUtils.java b/client/src/main/java/org/asynchttpclient/util/StringUtils.java new file mode 100644 index 0000000000..0abf0b686d --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/StringUtils.java @@ -0,0 +1,68 @@ +/* + * 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. + * 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.util; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; + +public final class StringUtils { + + private StringUtils() { + // Prevent outside initialization + } + + public static ByteBuffer charSequence2ByteBuffer(CharSequence cs, Charset charset) { + return charset.encode(CharBuffer.wrap(cs)); + } + + public static byte[] byteBuffer2ByteArray(ByteBuffer bb) { + byte[] rawBase = new byte[bb.remaining()]; + bb.get(rawBase); + return rawBase; + } + + public static byte[] charSequence2Bytes(CharSequence sb, Charset charset) { + ByteBuffer bb = charSequence2ByteBuffer(sb, charset); + return byteBuffer2ByteArray(bb); + } + + public static String toHexString(byte[] data) { + StringBuilder buffer = StringBuilderPool.DEFAULT.stringBuilder(); + for (byte aData : data) { + buffer.append(Integer.toHexString((aData & 0xf0) >>> 4)); + buffer.append(Integer.toHexString(aData & 0x0f)); + } + return buffer.toString(); + } + + public static void appendBase16(StringBuilder buf, byte[] bytes) { + int base = 16; + for (byte b : bytes) { + int bi = 0xff & b; + int c = '0' + bi / base % base; + if (c > '9') { + c = 'a' + c - '0' - 10; + } + buf.append((char) c); + c = '0' + bi % base; + if (c > '9') { + c = 'a' + c - '0' - 10; + } + buf.append((char) c); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/util/ThrowableUtil.java b/client/src/main/java/org/asynchttpclient/util/ThrowableUtil.java new file mode 100644 index 0000000000..7ca4ebbfaa --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/ThrowableUtil.java @@ -0,0 +1,35 @@ +/* + * 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.util; + +public final class ThrowableUtil { + + private ThrowableUtil() { + // Prevent outside initialization + } + + /** + * @param the Throwable type + * @param t the throwable whose stacktrace we want to remove + * @param clazz the caller class + * @param method the caller method + * @return the input throwable with removed stacktrace + */ + public static T unknownStackTrace(T t, Class clazz, String method) { + t.setStackTrace(new StackTraceElement[]{new StackTraceElement(clazz.getName(), method, null, -1)}); + return t; + } +} diff --git a/client/src/main/java/org/asynchttpclient/util/UriEncoder.java b/client/src/main/java/org/asynchttpclient/util/UriEncoder.java new file mode 100644 index 0000000000..92706d2926 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/UriEncoder.java @@ -0,0 +1,160 @@ +/* + * 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. + * 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.util; + +import org.asynchttpclient.Param; +import org.asynchttpclient.uri.Uri; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; +import static org.asynchttpclient.util.Utf8UrlEncoder.encodeAndAppendQuery; + +public enum UriEncoder { + + FIXING { + @Override + public String encodePath(String path) { + return Utf8UrlEncoder.encodePath(path); + } + + private void encodeAndAppendQueryParam(final StringBuilder sb, final CharSequence name, final @Nullable CharSequence value) { + Utf8UrlEncoder.encodeAndAppendQueryElement(sb, name); + if (value != null) { + sb.append('='); + Utf8UrlEncoder.encodeAndAppendQueryElement(sb, value); + } + sb.append('&'); + } + + private void encodeAndAppendQueryParams(final StringBuilder sb, final List queryParams) { + for (Param param : queryParams) { + encodeAndAppendQueryParam(sb, param.getName(), param.getValue()); + } + } + + @Override + protected String withQueryWithParams(final String query, final List queryParams) { + // concatenate encoded query + encoded query params + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); + encodeAndAppendQuery(sb, query); + sb.append('&'); + encodeAndAppendQueryParams(sb, queryParams); + sb.setLength(sb.length() - 1); + return sb.toString(); + } + + @Override + protected String withQueryWithoutParams(final String query) { + // encode query + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); + encodeAndAppendQuery(sb, query); + return sb.toString(); + } + + @Override + protected String withoutQueryWithParams(final List queryParams) { + // concatenate encoded query params + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); + encodeAndAppendQueryParams(sb, queryParams); + sb.setLength(sb.length() - 1); + return sb.toString(); + } + }, + + RAW { + @Override + public String encodePath(String path) { + return path; + } + + private void appendRawQueryParam(StringBuilder sb, String name, @Nullable String value) { + sb.append(name); + if (value != null) { + sb.append('=').append(value); + } + sb.append('&'); + } + + private void appendRawQueryParams(final StringBuilder sb, final List queryParams) { + for (Param param : queryParams) { + appendRawQueryParam(sb, param.getName(), param.getValue()); + } + } + + @Override + protected String withQueryWithParams(final String query, final List queryParams) { + // concatenate raw query + raw query params + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); + sb.append(query); + appendRawQueryParams(sb, queryParams); + sb.setLength(sb.length() - 1); + return sb.toString(); + } + + @Override + protected String withQueryWithoutParams(final String query) { + // return raw query as is + return query; + } + + @Override + protected String withoutQueryWithParams(final List queryParams) { + // concatenate raw queryParams + StringBuilder sb = StringBuilderPool.DEFAULT.stringBuilder(); + appendRawQueryParams(sb, queryParams); + sb.setLength(sb.length() - 1); + return sb.toString(); + } + }; + + public static UriEncoder uriEncoder(boolean disableUrlEncoding) { + return disableUrlEncoding ? RAW : FIXING; + } + + protected abstract String withQueryWithParams(String query, List queryParams); + + protected abstract String withQueryWithoutParams(String query); + + protected abstract String withoutQueryWithParams(List queryParams); + + private String withQuery(final String query, final @Nullable List queryParams) { + return isNonEmpty(queryParams) ? withQueryWithParams(query, queryParams) : withQueryWithoutParams(query); + } + + private @Nullable String withoutQuery(final @Nullable List queryParams) { + return isNonEmpty(queryParams) ? withoutQueryWithParams(queryParams) : null; + } + + public Uri encode(Uri uri, @Nullable List queryParams) { + String newPath = encodePath(uri.getPath()); + String newQuery = encodeQuery(uri.getQuery(), queryParams); + return new Uri(uri.getScheme(), + uri.getUserInfo(), + uri.getHost(), + uri.getPort(), + newPath, + newQuery, + uri.getFragment()); + } + + protected abstract String encodePath(String path); + + private @Nullable String encodeQuery(final @Nullable String query, final @Nullable List queryParams) { + return isNonEmpty(query) ? withQuery(query, queryParams) : withoutQuery(queryParams); + } +} diff --git a/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java b/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java new file mode 100644 index 0000000000..fe01e32087 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java @@ -0,0 +1,241 @@ +/* + * 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.util; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; + +import java.util.BitSet; + +public final class Utf8UrlEncoder { + + // see http://tools.ietf.org/html/rfc3986#section-3.4 + // ALPHA / DIGIT / "-" / "." / "_" / "~" + private static final BitSet RFC3986_UNRESERVED_CHARS = new BitSet(); + // gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" + private static final BitSet RFC3986_GENDELIM_CHARS = new BitSet(); + // "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + private static final BitSet RFC3986_SUBDELIM_CHARS = new BitSet(); + // gen-delims / sub-delims + private static final BitSet RFC3986_RESERVED_CHARS = new BitSet(); + // unreserved / pct-encoded / sub-delims / ":" / "@" + private static final BitSet RFC3986_PCHARS = new BitSet(); + private static final BitSet BUILT_PATH_UNTOUCHED_CHARS = new BitSet(); + private static final BitSet BUILT_QUERY_UNTOUCHED_CHARS = new BitSet(); + // http://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm + private static final BitSet FORM_URL_ENCODED_SAFE_CHARS = new BitSet(); + private static final char[] HEX = "0123456789ABCDEF".toCharArray(); + + static { + for (int i = 'a'; i <= 'z'; ++i) { + RFC3986_UNRESERVED_CHARS.set(i); + } + for (int i = 'A'; i <= 'Z'; ++i) { + RFC3986_UNRESERVED_CHARS.set(i); + } + for (int i = '0'; i <= '9'; ++i) { + RFC3986_UNRESERVED_CHARS.set(i); + } + RFC3986_UNRESERVED_CHARS.set('-'); + RFC3986_UNRESERVED_CHARS.set('.'); + RFC3986_UNRESERVED_CHARS.set('_'); + RFC3986_UNRESERVED_CHARS.set('~'); + } + + static { + RFC3986_GENDELIM_CHARS.set(':'); + RFC3986_GENDELIM_CHARS.set('/'); + RFC3986_GENDELIM_CHARS.set('?'); + RFC3986_GENDELIM_CHARS.set('#'); + RFC3986_GENDELIM_CHARS.set('['); + RFC3986_GENDELIM_CHARS.set(']'); + RFC3986_GENDELIM_CHARS.set('@'); + } + + static { + RFC3986_SUBDELIM_CHARS.set('!'); + RFC3986_SUBDELIM_CHARS.set('$'); + RFC3986_SUBDELIM_CHARS.set('&'); + RFC3986_SUBDELIM_CHARS.set('\''); + RFC3986_SUBDELIM_CHARS.set('('); + RFC3986_SUBDELIM_CHARS.set(')'); + RFC3986_SUBDELIM_CHARS.set('*'); + RFC3986_SUBDELIM_CHARS.set('+'); + RFC3986_SUBDELIM_CHARS.set(','); + RFC3986_SUBDELIM_CHARS.set(';'); + RFC3986_SUBDELIM_CHARS.set('='); + } + + static { + RFC3986_RESERVED_CHARS.or(RFC3986_GENDELIM_CHARS); + RFC3986_RESERVED_CHARS.or(RFC3986_SUBDELIM_CHARS); + } + + static { + RFC3986_PCHARS.or(RFC3986_UNRESERVED_CHARS); + RFC3986_PCHARS.or(RFC3986_SUBDELIM_CHARS); + RFC3986_PCHARS.set(':'); + RFC3986_PCHARS.set('@'); + } + + static { + BUILT_PATH_UNTOUCHED_CHARS.or(RFC3986_PCHARS); + BUILT_PATH_UNTOUCHED_CHARS.set('%'); + BUILT_PATH_UNTOUCHED_CHARS.set('/'); + } + + static { + BUILT_QUERY_UNTOUCHED_CHARS.or(RFC3986_PCHARS); + BUILT_QUERY_UNTOUCHED_CHARS.set('%'); + BUILT_QUERY_UNTOUCHED_CHARS.set('/'); + BUILT_QUERY_UNTOUCHED_CHARS.set('?'); + } + + static { + for (int i = 'a'; i <= 'z'; ++i) { + FORM_URL_ENCODED_SAFE_CHARS.set(i); + } + for (int i = 'A'; i <= 'Z'; ++i) { + FORM_URL_ENCODED_SAFE_CHARS.set(i); + } + for (int i = '0'; i <= '9'; ++i) { + FORM_URL_ENCODED_SAFE_CHARS.set(i); + } + + FORM_URL_ENCODED_SAFE_CHARS.set('-'); + FORM_URL_ENCODED_SAFE_CHARS.set('.'); + FORM_URL_ENCODED_SAFE_CHARS.set('_'); + FORM_URL_ENCODED_SAFE_CHARS.set('*'); + } + + private Utf8UrlEncoder() { + } + + public static String encodePath(String input) { + StringBuilder sb = lazyAppendEncoded(null, input, BUILT_PATH_UNTOUCHED_CHARS, false); + return sb == null ? input : sb.toString(); + } + + public static StringBuilder encodeAndAppendQuery(StringBuilder sb, String query) { + return appendEncoded(sb, query, BUILT_QUERY_UNTOUCHED_CHARS, false); + } + + public static String encodeQueryElement(String input) { + StringBuilder sb = new StringBuilder(input.length() + 6); + encodeAndAppendQueryElement(sb, input); + return sb.toString(); + } + + public static StringBuilder encodeAndAppendQueryElement(StringBuilder sb, CharSequence input) { + return appendEncoded(sb, input, FORM_URL_ENCODED_SAFE_CHARS, false); + } + + public static StringBuilder encodeAndAppendFormElement(StringBuilder sb, CharSequence input) { + return appendEncoded(sb, input, FORM_URL_ENCODED_SAFE_CHARS, true); + } + + @Contract("!null -> !null") + public static @Nullable String percentEncodeQueryElement(@Nullable String input) { + if (input == null) { + return null; + } + StringBuilder sb = new StringBuilder(input.length() + 6); + encodeAndAppendPercentEncoded(sb, input); + return sb.toString(); + } + + public static StringBuilder encodeAndAppendPercentEncoded(StringBuilder sb, CharSequence input) { + return appendEncoded(sb, input, RFC3986_UNRESERVED_CHARS, false); + } + + private static StringBuilder lazyInitStringBuilder(CharSequence input, int firstNonUsAsciiPosition) { + StringBuilder sb = new StringBuilder(input.length() + 6); + for (int i = 0; i < firstNonUsAsciiPosition; i++) { + sb.append(input.charAt(i)); + } + return sb; + } + + private static @Nullable StringBuilder lazyAppendEncoded(@Nullable StringBuilder sb, CharSequence input, BitSet dontNeedEncoding, boolean encodeSpaceAsPlus) { + int c; + for (int i = 0; i < input.length(); i += Character.charCount(c)) { + c = Character.codePointAt(input, i); + if (c <= 127) { + if (dontNeedEncoding.get(c)) { + if (sb != null) { + sb.append((char) c); + } + } else { + if (sb == null) { + sb = lazyInitStringBuilder(input, i); + } + appendSingleByteEncoded(sb, c, encodeSpaceAsPlus); + } + } else { + if (sb == null) { + sb = lazyInitStringBuilder(input, i); + } + appendMultiByteEncoded(sb, c); + } + } + return sb; + } + + private static StringBuilder appendEncoded(StringBuilder sb, CharSequence input, BitSet dontNeedEncoding, boolean encodeSpaceAsPlus) { + int c; + for (int i = 0; i < input.length(); i += Character.charCount(c)) { + c = Character.codePointAt(input, i); + if (c <= 127) { + if (dontNeedEncoding.get(c)) { + sb.append((char) c); + } else { + appendSingleByteEncoded(sb, c, encodeSpaceAsPlus); + } + } else { + appendMultiByteEncoded(sb, c); + } + } + return sb; + } + + private static void appendSingleByteEncoded(StringBuilder sb, int value, boolean encodeSpaceAsPlus) { + + if (value == ' ' && encodeSpaceAsPlus) { + sb.append('+'); + return; + } + + sb.append('%'); + sb.append(HEX[value >> 4]); + sb.append(HEX[value & 0xF]); + } + + private static void appendMultiByteEncoded(StringBuilder sb, int value) { + if (value < 0x800) { + appendSingleByteEncoded(sb, 0xc0 | value >> 6, false); + appendSingleByteEncoded(sb, 0x80 | value & 0x3f, false); + } else if (value < 0x10000) { + appendSingleByteEncoded(sb, 0xe0 | value >> 12, false); + appendSingleByteEncoded(sb, 0x80 | value >> 6 & 0x3f, false); + appendSingleByteEncoded(sb, 0x80 | value & 0x3f, false); + } else { + appendSingleByteEncoded(sb, 0xf0 | value >> 18, false); + appendSingleByteEncoded(sb, 0x80 | value >> 12 & 0x3f, false); + appendSingleByteEncoded(sb, 0x80 | value >> 6 & 0x3f, false); + appendSingleByteEncoded(sb, 0x80 | value & 0x3f, false); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/ws/WebSocket.java b/client/src/main/java/org/asynchttpclient/ws/WebSocket.java new file mode 100644 index 0000000000..0362ba4551 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/ws/WebSocket.java @@ -0,0 +1,216 @@ +/* + * 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.ws; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.util.concurrent.Future; + +import java.net.SocketAddress; + +/** + * A WebSocket client + */ +public interface WebSocket { + + /** + * @return the headers received in the Upgrade response + */ + HttpHeaders getUpgradeHeaders(); + + /** + * Get remote address client initiated request to. + * + * @return remote address client initiated request to, may be {@code null} if asynchronous provider is unable to provide the remote address + */ + SocketAddress getRemoteAddress(); + + /** + * Get local address client initiated request from. + * + * @return local address client initiated request from, may be {@code null} if asynchronous provider is unable to provide the local address + */ + SocketAddress getLocalAddress(); + + /** + * Send a full text frame + * + * @param payload a text payload + * @return a future that will be completed once the frame will be actually written on the wire + */ + Future sendTextFrame(String payload); + + /** + * 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 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 + */ + Future sendTextFrame(String payload, boolean finalFragment, int rsv); + + /** + * 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 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 + */ + Future sendTextFrame(ByteBuf payload, boolean finalFragment, int rsv); + + /** + * Send a full binary frame. + * + * @param payload a binary payload + * @return a future that will be completed once the frame will be actually written on the wire + */ + Future sendBinaryFrame(byte[] payload); + + /** + * 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 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 sendBinaryFrame(byte[] payload, boolean finalFragment, int rsv); + + /** + * 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 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 sendBinaryFrame(ByteBuf payload, boolean finalFragment, int rsv); + + /** + * Send a text continuation frame. The last fragment must have finalFragment set to true. + * + * @param payload the text 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(String payload, boolean finalFragment, int rsv); + + /** + * Send a binary continuation frame. The last fragment must have finalFragment set to true. + * + * @param payload the binary 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(byte[] payload, boolean finalFragment, int rsv); + + /** + * 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 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 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 fewer). + * + * @param payload the payload. + * @return a future that will be completed once the frame will be actually written on the wire + */ + Future sendPingFrame(byte[] payload); + + /** + * 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 + */ + Future sendPingFrame(ByteBuf payload); + + /** + * 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 fewer). + * + * @param payload the payload. + * @return a future that will be completed once the frame will be actually written on the wire + */ + Future sendPongFrame(byte[] payload); + + /** + * 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 + */ + Future sendPongFrame(ByteBuf payload); + + /** + * 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 an empty close frame. + * + * @param statusCode a status code + * @param reasonText a reason + * @return a future that will be completed once the frame will be actually written on the wire + */ + Future sendCloseFrame(int statusCode, String reasonText); + + /** + * @return {@code true} if the WebSocket is open/connected. + */ + boolean isOpen(); + + /** + * Add a {@link WebSocketListener} + * + * @param l a {@link WebSocketListener} + * @return this + */ + WebSocket addWebSocketListener(WebSocketListener l); + + /** + * Remove a {@link WebSocketListener} + * + * @param l a {@link WebSocketListener} + * @return this + */ + WebSocket removeWebSocketListener(WebSocketListener l); +} diff --git a/client/src/main/java/org/asynchttpclient/ws/WebSocketListener.java b/client/src/main/java/org/asynchttpclient/ws/WebSocketListener.java new file mode 100644 index 0000000000..7d92a66199 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/ws/WebSocketListener.java @@ -0,0 +1,79 @@ +/* + * 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.ws; + +/** + * A generic {@link WebSocketListener} for WebSocket events. Use the appropriate listener for receiving message bytes. + */ +public interface WebSocketListener { + + /** + * Invoked when the {@link WebSocket} is open. + * + * @param websocket the WebSocket + */ + void onOpen(WebSocket websocket); + + /** + * Invoked when the {@link WebSocket} is closed. + * + * @param websocket the WebSocket + * @param code the status code + * @param reason the reason message + * @see "http://tools.ietf.org/html/rfc6455#section-5.5.1" + */ + void onClose(WebSocket websocket, int code, String reason); + + /** + * Invoked when the {@link WebSocket} crashes. + * + * @param t a {@link Throwable} + */ + void onError(Throwable t); + + /** + * Invoked when a binary frame is received. + * + * @param payload a byte array + * @param finalFragment true if this frame is the final fragment + * @param rsv extension bits + */ + default void onBinaryFrame(byte[] payload, boolean finalFragment, int rsv) { + } + + /** + * Invoked when a text frame is received. + * + * @param payload a UTF-8 {@link String} message + * @param finalFragment true if this frame is the final fragment + * @param rsv extension bits + */ + default void onTextFrame(String payload, boolean finalFragment, int rsv) { + } + + /** + * Invoked when a ping frame is received + * + * @param payload a byte array + */ + default void onPingFrame(byte[] payload) { + } + + /** + * Invoked when a pong frame is received + * + * @param payload a byte array + */ + default void onPongFrame(byte[] payload) { + } +} diff --git a/client/src/main/java/org/asynchttpclient/ws/WebSocketUpgradeHandler.java b/client/src/main/java/org/asynchttpclient/ws/WebSocketUpgradeHandler.java new file mode 100644 index 0000000000..b4c6e1a44a --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/ws/WebSocketUpgradeHandler.java @@ -0,0 +1,154 @@ +/* + * 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.ws; + +import io.netty.handler.codec.http.HttpHeaders; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.netty.ws.NettyWebSocket; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +import static org.asynchttpclient.util.HttpConstants.ResponseStatusCodes.SWITCHING_PROTOCOLS_101; + +/** + * An {@link AsyncHandler} which is able to execute WebSocket upgrade. Use the Builder for configuring WebSocket options. + */ +public class WebSocketUpgradeHandler implements AsyncHandler { + + private final List listeners; + private @Nullable NettyWebSocket webSocket; + + public WebSocketUpgradeHandler(List listeners) { + this.listeners = listeners; + } + + protected void setWebSocket0(NettyWebSocket webSocket) { + } + + protected void onStatusReceived0(HttpResponseStatus responseStatus) throws Exception { + } + + protected void onHeadersReceived0(HttpHeaders headers) throws Exception { + } + + protected void onBodyPartReceived0(HttpResponseBodyPart bodyPart) throws Exception { + } + + protected void onCompleted0() throws Exception { + } + + protected void onThrowable0(Throwable t) { + } + + protected void onOpen0() { + } + + @Override + public final State onStatusReceived(HttpResponseStatus responseStatus) throws Exception { + onStatusReceived0(responseStatus); + return responseStatus.getStatusCode() == SWITCHING_PROTOCOLS_101 ? State.CONTINUE : State.ABORT; + } + + @Override + public final State onHeadersReceived(HttpHeaders headers) throws Exception { + onHeadersReceived0(headers); + return State.CONTINUE; + } + + @Override + public final State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { + onBodyPartReceived0(bodyPart); + return State.CONTINUE; + } + + @Override + public final @Nullable NettyWebSocket onCompleted() throws Exception { + onCompleted0(); + return webSocket; + } + + @Override + public final void onThrowable(Throwable t) { + onThrowable0(t); + for (WebSocketListener listener : listeners) { + if (webSocket != null) { + webSocket.addWebSocketListener(listener); + } + listener.onError(t); + } + } + + public final void setWebSocket(NettyWebSocket webSocket) { + this.webSocket = webSocket; + setWebSocket0(webSocket); + } + + /** + * @param webSocket this parameter is the same object as the field webSocket, + * but guaranteed to be not null. This is done to satisfy NullAway requirements + */ + public final void onOpen(NettyWebSocket webSocket) { + onOpen0(); + for (WebSocketListener listener : listeners) { + webSocket.addWebSocketListener(listener); + listener.onOpen(webSocket); + } + webSocket.processBufferedFrames(); + } + + /** + * Build a {@link WebSocketUpgradeHandler} + */ + public static final class Builder { + + private final List listeners = new ArrayList<>(1); + + /** + * Add a {@link WebSocketListener} that will be added to the {@link WebSocket} + * + * @param listener a {@link WebSocketListener} + * @return this + */ + public Builder addWebSocketListener(WebSocketListener listener) { + listeners.add(listener); + return this; + } + + /** + * Remove a {@link WebSocketListener} + * + * @param listener a {@link WebSocketListener} + * @return this + */ + public Builder removeWebSocketListener(WebSocketListener listener) { + listeners.remove(listener); + return this; + } + + /** + * Build a {@link WebSocketUpgradeHandler} + * + * @return a {@link WebSocketUpgradeHandler} + */ + public WebSocketUpgradeHandler build() { + return new WebSocketUpgradeHandler(listeners); + } + } +} diff --git a/client/src/main/java/org/asynchttpclient/ws/WebSocketUtils.java b/client/src/main/java/org/asynchttpclient/ws/WebSocketUtils.java new file mode 100644 index 0000000000..628cc1d7df --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/ws/WebSocketUtils.java @@ -0,0 +1,44 @@ +/* + * 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. + * 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.ws; + +import io.netty.util.internal.ThreadLocalRandom; + +import java.util.Base64; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.asynchttpclient.util.MessageDigestUtils.pooledSha1MessageDigest; + +public final class WebSocketUtils { + private static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + private WebSocketUtils() { + // Prevent outside initialization + } + + public static String getWebSocketKey() { + byte[] nonce = new byte[16]; + ThreadLocalRandom random = ThreadLocalRandom.current(); + for (int i = 0; i < nonce.length; i++) { + nonce[i] = (byte) random.nextInt(256); + } + return Base64.getEncoder().encodeToString(nonce); + } + + public static String getAcceptKey(String key) { + return Base64.getEncoder().encodeToString(pooledSha1MessageDigest().digest((key + MAGIC_GUID).getBytes(US_ASCII))); + } +} diff --git a/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties b/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties new file mode 100644 index 0000000000..f74127c23d --- /dev/null +++ b/client/src/main/resources/org/asynchttpclient/config/ahc-default.properties @@ -0,0 +1,57 @@ +org.asynchttpclient.threadPoolName=AsyncHttpClient +org.asynchttpclient.maxConnections=-1 +org.asynchttpclient.maxConnectionsPerHost=-1 +org.asynchttpclient.acquireFreeChannelTimeout=0 +org.asynchttpclient.connectTimeout=PT5S +org.asynchttpclient.pooledConnectionIdleTimeout=PT1M +org.asynchttpclient.connectionPoolCleanerPeriod=PT0.1S +org.asynchttpclient.readTimeout=PT1M +org.asynchttpclient.requestTimeout=PT1M +org.asynchttpclient.connectionTtl=-PT0.001S +org.asynchttpclient.followRedirect=false +org.asynchttpclient.maxRedirects=5 +org.asynchttpclient.compressionEnforced=false +org.asynchttpclient.enableAutomaticDecompression=true +org.asynchttpclient.userAgent=AHC/2.1 +org.asynchttpclient.enabledProtocols=TLSv1.3, TLSv1.2 +org.asynchttpclient.enabledCipherSuites= +org.asynchttpclient.filterInsecureCipherSuites=true +org.asynchttpclient.useProxySelector=false +org.asynchttpclient.useProxyProperties=false +org.asynchttpclient.validateResponseHeaders=true +org.asynchttpclient.aggregateWebSocketFrameFragments=true +org.asynchttpclient.strict302Handling=false +org.asynchttpclient.keepAlive=true +org.asynchttpclient.maxRequestRetry=5 +org.asynchttpclient.disableUrlEncodingForBoundRequests=false +org.asynchttpclient.useLaxCookieEncoder=false +org.asynchttpclient.removeQueryParamOnRedirect=true +org.asynchttpclient.useOpenSsl=false +org.asynchttpclient.useInsecureTrustManager=false +org.asynchttpclient.disableHttpsEndpointIdentificationAlgorithm=false +org.asynchttpclient.sslSessionCacheSize=0 +org.asynchttpclient.sslSessionTimeout=0 +org.asynchttpclient.tcpNoDelay=true +org.asynchttpclient.soReuseAddress=false +org.asynchttpclient.soKeepAlive=true +org.asynchttpclient.soLinger=-1 +org.asynchttpclient.soSndBuf=-1 +org.asynchttpclient.soRcvBuf=-1 +org.asynchttpclient.httpClientCodecMaxInitialLineLength=4096 +org.asynchttpclient.httpClientCodecMaxHeaderSize=8192 +org.asynchttpclient.httpClientCodecMaxChunkSize=8192 +org.asynchttpclient.httpClientCodecInitialBufferSize=128 +org.asynchttpclient.disableZeroCopy=false +org.asynchttpclient.handshakeTimeout=10000 +org.asynchttpclient.chunkedFileChunkSize=8192 +org.asynchttpclient.webSocketMaxBufferSize=128000000 +org.asynchttpclient.webSocketMaxFrameSize=10240 +org.asynchttpclient.keepEncodingHeader=false +org.asynchttpclient.shutdownQuietPeriod=PT2S +org.asynchttpclient.shutdownTimeout=PT15S +org.asynchttpclient.useNativeTransport=false +org.asynchttpclient.useOnlyEpollNativeTransport=false +org.asynchttpclient.ioThreadsCount=-1 +org.asynchttpclient.hashedWheelTimerTickDuration=100 +org.asynchttpclient.hashedWheelTimerSize=512 +org.asynchttpclient.expiredCookieEvictionDelay=30000 diff --git a/api/src/main/resources/org/asynchttpclient/version.properties b/client/src/main/resources/org/asynchttpclient/config/ahc-version.properties similarity index 100% rename from api/src/main/resources/org/asynchttpclient/version.properties rename to client/src/main/resources/org/asynchttpclient/config/ahc-version.properties diff --git a/client/src/main/resources/org/asynchttpclient/request/body/multipart/ahc-mime.types b/client/src/main/resources/org/asynchttpclient/request/body/multipart/ahc-mime.types new file mode 100644 index 0000000000..4ec2dfc6b8 --- /dev/null +++ b/client/src/main/resources/org/asynchttpclient/request/body/multipart/ahc-mime.types @@ -0,0 +1,1832 @@ +# This file maps Internet media types to unique file extension(s). +# Although created for httpd, this file is used by many software systems +# and has been placed in the public domain for unlimited redisribution. +# +# The table below contains both registered and (common) unregistered types. +# A type that has no unique extension can be ignored -- they are listed +# here to guide configurations toward known types and to make it easier to +# identify "new" types. File extensions are also commonly used to indicate +# content languages and encodings, so choose them carefully. +# +# Internet media types should be registered as described in RFC 4288. +# The registry is at . +# +# MIME type (lowercased) Extensions +# ============================================ ========== +# application/1d-interleaved-parityfec +# application/3gpdash-qoe-report+xml +# application/3gpp-ims+xml +# application/a2l +# application/activemessage +# application/alto-costmap+json +# application/alto-costmapfilter+json +# application/alto-directory+json +# application/alto-endpointcost+json +# application/alto-endpointcostparams+json +# application/alto-endpointprop+json +# application/alto-endpointpropparams+json +# application/alto-error+json +# application/alto-networkmap+json +# application/alto-networkmapfilter+json +# application/aml +application/andrew-inset ez +# application/applefile +application/applixware aw +# application/atf +# application/atfx +application/atom+xml atom +application/atomcat+xml atomcat +# application/atomdeleted+xml +# application/atomicmail +application/atomsvc+xml atomsvc +# application/atxml +# application/auth-policy+xml +# application/bacnet-xdd+zip +# application/batch-smtp +# application/beep+xml +# application/calendar+json +# application/calendar+xml +# application/call-completion +# application/cals-1840 +# application/cbor +# application/ccmp+xml +application/ccxml+xml ccxml +# application/cdfx+xml +application/cdmi-capability cdmia +application/cdmi-container cdmic +application/cdmi-domain cdmid +application/cdmi-object cdmio +application/cdmi-queue cdmiq +# application/cdni +# application/cea +# application/cea-2018+xml +# application/cellml+xml +# application/cfw +# application/cms +# application/cnrp+xml +# application/coap-group+json +# application/commonground +# application/conference-info+xml +# application/cpl+xml +# application/csrattrs +# application/csta+xml +# application/cstadata+xml +# application/csvm+json +application/cu-seeme cu +# application/cybercash +# application/dash+xml +# application/dashdelta +application/davmount+xml davmount +# application/dca-rft +# application/dcd +# application/dec-dx +# application/dialog-info+xml +# application/dicom +# application/dii +# application/dit +# application/dns +application/docbook+xml dbk +# application/dskpp+xml +application/dssc+der dssc +application/dssc+xml xdssc +# application/dvcs +application/ecmascript ecma +# application/edi-consent +# application/edi-x12 +# application/edifact +# application/emergencycalldata.comment+xml +# application/emergencycalldata.deviceinfo+xml +# application/emergencycalldata.providerinfo+xml +# application/emergencycalldata.serviceinfo+xml +# application/emergencycalldata.subscriberinfo+xml +application/emma+xml emma +# application/emotionml+xml +# application/encaprtp +# application/epp+xml +application/epub+zip epub +# application/eshop +# application/example +application/exi exi +# application/fastinfoset +# application/fastsoap +# application/fdt+xml +# application/fits +# application/font-sfnt +application/font-tdpfr pfr +application/font-woff woff +# application/framework-attributes+xml +application/gml+xml gml +application/gpx+xml gpx +application/gxf gxf +# application/gzip +# application/h224 +# application/held+xml +# application/http +application/hyperstudio stk +# application/ibe-key-request+xml +# application/ibe-pkg-reply+xml +# application/ibe-pp-data +# application/iges +# application/im-iscomposing+xml +# application/index +# application/index.cmd +# application/index.obj +# application/index.response +# application/index.vnd +application/inkml+xml ink inkml +# application/iotp +application/ipfix ipfix +# application/ipp +# application/isup +# application/its+xml +application/java-archive jar +application/java-serialized-object ser +application/java-vm class +application/javascript js +# application/jose +# application/jose+json +# application/jrd+json +application/json json +# application/json-patch+json +# application/json-seq +application/jsonml+json jsonml +# application/jwk+json +# application/jwk-set+json +# application/jwt +# application/kpml-request+xml +# application/kpml-response+xml +# application/ld+json +# application/link-format +# application/load-control+xml +application/lost+xml lostxml +# application/lostsync+xml +# application/lxf +application/mac-binhex40 hqx +application/mac-compactpro cpt +# application/macwriteii +application/mads+xml mads +application/marc mrc +application/marcxml+xml mrcx +application/mathematica ma nb mb +application/mathml+xml mathml +# application/mathml-content+xml +# application/mathml-presentation+xml +# application/mbms-associated-procedure-description+xml +# application/mbms-deregister+xml +# application/mbms-envelope+xml +# application/mbms-msk+xml +# application/mbms-msk-response+xml +# application/mbms-protection-description+xml +# application/mbms-reception-report+xml +# application/mbms-register+xml +# application/mbms-register-response+xml +# application/mbms-schedule+xml +# application/mbms-user-service-description+xml +application/mbox mbox +# application/media-policy-dataset+xml +# application/media_control+xml +application/mediaservercontrol+xml mscml +# application/merge-patch+json +application/metalink+xml metalink +application/metalink4+xml meta4 +application/mets+xml mets +# application/mf4 +# application/mikey +application/mods+xml mods +# application/moss-keys +# application/moss-signature +# application/mosskey-data +# application/mosskey-request +application/mp21 m21 mp21 +application/mp4 mp4s +# application/mpeg4-generic +# application/mpeg4-iod +# application/mpeg4-iod-xmt +# application/mrb-consumer+xml +# application/mrb-publish+xml +# application/msc-ivr+xml +# application/msc-mixer+xml +application/msword doc dot +application/mxf mxf +# application/nasdata +# application/news-checkgroups +# application/news-groupinfo +# application/news-transmission +# application/nlsml+xml +# application/nss +# application/ocsp-request +# application/ocsp-response +application/octet-stream bin dms lrf mar so dist distz pkg bpk dump elc deploy +application/oda oda +# application/odx +application/oebps-package+xml opf +application/ogg ogx +application/omdoc+xml omdoc +application/onenote onetoc onetoc2 onetmp onepkg +application/oxps oxps +# application/p2p-overlay+xml +# application/parityfec +application/patch-ops-error+xml xer +application/pdf pdf +# application/pdx +application/pgp-encrypted pgp +# application/pgp-keys +application/pgp-signature asc sig +application/pics-rules prf +# application/pidf+xml +# application/pidf-diff+xml +application/pkcs10 p10 +# application/pkcs12 +application/pkcs7-mime p7m p7c +application/pkcs7-signature p7s +application/pkcs8 p8 +application/pkix-attr-cert ac +application/pkix-cert cer +application/pkix-crl crl +application/pkix-pkipath pkipath +application/pkixcmp pki +application/pls+xml pls +# application/poc-settings+xml +application/postscript ai eps ps +# application/ppsp-tracker+json +# application/problem+json +# application/problem+xml +# application/provenance+xml +# application/prs.alvestrand.titrax-sheet +application/prs.cww cww +# application/prs.hpub+zip +# application/prs.nprend +# application/prs.plucker +# application/prs.rdf-xml-crypt +# application/prs.xsf+xml +application/pskc+xml pskcxml +# application/qsig +# application/raptorfec +# application/rdap+json +application/rdf+xml rdf +application/reginfo+xml rif +application/relax-ng-compact-syntax rnc +# application/remote-printing +# application/reputon+json +application/resource-lists+xml rl +application/resource-lists-diff+xml rld +# application/rfc+xml +# application/riscos +# application/rlmi+xml +application/rls-services+xml rs +application/rpki-ghostbusters gbr +application/rpki-manifest mft +application/rpki-roa roa +# application/rpki-updown +application/rsd+xml rsd +application/rss+xml rss +application/rtf rtf +# application/rtploopback +# application/rtx +# application/samlassertion+xml +# application/samlmetadata+xml +application/sbml+xml sbml +# application/scaip+xml +# application/scim+json +application/scvp-cv-request scq +application/scvp-cv-response scs +application/scvp-vp-request spq +application/scvp-vp-response spp +application/sdp sdp +# application/sep+xml +# application/sep-exi +# application/session-info +# application/set-payment +application/set-payment-initiation setpay +# application/set-registration +application/set-registration-initiation setreg +# application/sgml +# application/sgml-open-catalog +application/shf+xml shf +# application/sieve +# application/simple-filter+xml +# application/simple-message-summary +# application/simplesymbolcontainer +# application/slate +# application/smil +application/smil+xml smi smil +# application/smpte336m +# application/soap+fastinfoset +# application/soap+xml +application/sparql-query rq +application/sparql-results+xml srx +# application/spirits-event+xml +# application/sql +application/srgs gram +application/srgs+xml grxml +application/sru+xml sru +application/ssdl+xml ssdl +application/ssml+xml ssml +# application/tamp-apex-update +# application/tamp-apex-update-confirm +# application/tamp-community-update +# application/tamp-community-update-confirm +# application/tamp-error +# application/tamp-sequence-adjust +# application/tamp-sequence-adjust-confirm +# application/tamp-status-query +# application/tamp-status-response +# application/tamp-update +# application/tamp-update-confirm +application/tei+xml tei teicorpus +application/thraud+xml tfi +# application/timestamp-query +# application/timestamp-reply +application/timestamped-data tsd +# application/ttml+xml +# application/tve-trigger +# application/ulpfec +# application/urc-grpsheet+xml +# application/urc-ressheet+xml +# application/urc-targetdesc+xml +# application/urc-uisocketdesc+xml +# application/vcard+json +# application/vcard+xml +# application/vemmi +# application/vividence.scriptfile +# application/vnd.3gpp-prose+xml +# application/vnd.3gpp-prose-pc3ch+xml +# application/vnd.3gpp.access-transfer-events+xml +# application/vnd.3gpp.bsf+xml +# application/vnd.3gpp.mid-call+xml +application/vnd.3gpp.pic-bw-large plb +application/vnd.3gpp.pic-bw-small psb +application/vnd.3gpp.pic-bw-var pvb +# application/vnd.3gpp.sms +# application/vnd.3gpp.srvcc-ext+xml +# application/vnd.3gpp.srvcc-info+xml +# application/vnd.3gpp.state-and-event-info+xml +# application/vnd.3gpp.ussd+xml +# application/vnd.3gpp2.bcmcsinfo+xml +# application/vnd.3gpp2.sms +application/vnd.3gpp2.tcap tcap +application/vnd.3m.post-it-notes pwn +application/vnd.accpac.simply.aso aso +application/vnd.accpac.simply.imp imp +application/vnd.acucobol acu +application/vnd.acucorp atc acutc +application/vnd.adobe.air-application-installer-package+zip air +# application/vnd.adobe.flash.movie +application/vnd.adobe.formscentral.fcdt fcdt +application/vnd.adobe.fxp fxp fxpl +# application/vnd.adobe.partial-upload +application/vnd.adobe.xdp+xml xdp +application/vnd.adobe.xfdf xfdf +# application/vnd.aether.imp +# application/vnd.ah-barcode +application/vnd.ahead.space ahead +application/vnd.airzip.filesecure.azf azf +application/vnd.airzip.filesecure.azs azs +application/vnd.amazon.ebook azw +application/vnd.americandynamics.acc acc +application/vnd.amiga.ami ami +# application/vnd.amundsen.maze+xml +application/vnd.android.package-archive apk +# application/vnd.anki +application/vnd.anser-web-certificate-issue-initiation cii +application/vnd.anser-web-funds-transfer-initiation fti +application/vnd.antix.game-component atx +# application/vnd.apache.thrift.binary +# application/vnd.apache.thrift.compact +# application/vnd.apache.thrift.json +# application/vnd.api+json +application/vnd.apple.installer+xml mpkg +application/vnd.apple.mpegurl m3u8 +# application/vnd.arastra.swi +application/vnd.aristanetworks.swi swi +# application/vnd.artsquare +application/vnd.astraea-software.iota iota +application/vnd.audiograph aep +# application/vnd.autopackage +# application/vnd.avistar+xml +# application/vnd.balsamiq.bmml+xml +# application/vnd.balsamiq.bmpr +# application/vnd.bekitzur-stech+json +# application/vnd.biopax.rdf+xml +application/vnd.blueice.multipass mpm +# application/vnd.bluetooth.ep.oob +# application/vnd.bluetooth.le.oob +application/vnd.bmi bmi +application/vnd.businessobjects rep +# application/vnd.cab-jscript +# application/vnd.canon-cpdl +# application/vnd.canon-lips +# application/vnd.cendio.thinlinc.clientconf +# application/vnd.century-systems.tcp_stream +application/vnd.chemdraw+xml cdxml +application/vnd.chipnuts.karaoke-mmd mmd +application/vnd.cinderella cdy +# application/vnd.cirpack.isdn-ext +# application/vnd.citationstyles.style+xml +application/vnd.claymore cla +application/vnd.cloanto.rp9 rp9 +application/vnd.clonk.c4group c4g c4d c4f c4p c4u +application/vnd.cluetrust.cartomobile-config c11amc +application/vnd.cluetrust.cartomobile-config-pkg c11amz +# application/vnd.coffeescript +# application/vnd.collection+json +# application/vnd.collection.doc+json +# application/vnd.collection.next+json +# application/vnd.commerce-battelle +application/vnd.commonspace csp +application/vnd.contact.cmsg cdbcmsg +application/vnd.cosmocaller cmc +application/vnd.crick.clicker clkx +application/vnd.crick.clicker.keyboard clkk +application/vnd.crick.clicker.palette clkp +application/vnd.crick.clicker.template clkt +application/vnd.crick.clicker.wordbank clkw +application/vnd.criticaltools.wbs+xml wbs +application/vnd.ctc-posml pml +# application/vnd.ctct.ws+xml +# application/vnd.cups-pdf +# application/vnd.cups-postscript +application/vnd.cups-ppd ppd +# application/vnd.cups-raster +# application/vnd.cups-raw +# application/vnd.curl +application/vnd.curl.car car +application/vnd.curl.pcurl pcurl +# application/vnd.cyan.dean.root+xml +# application/vnd.cybank +application/vnd.dart dart +application/vnd.data-vision.rdz rdz +# application/vnd.debian.binary-package +application/vnd.dece.data uvf uvvf uvd uvvd +application/vnd.dece.ttml+xml uvt uvvt +application/vnd.dece.unspecified uvx uvvx +application/vnd.dece.zip uvz uvvz +application/vnd.denovo.fcselayout-link fe_launch +# application/vnd.desmume.movie +# application/vnd.dir-bi.plate-dl-nosuffix +# application/vnd.dm.delegation+xml +application/vnd.dna dna +# application/vnd.document+json +application/vnd.dolby.mlp mlp +# application/vnd.dolby.mobile.1 +# application/vnd.dolby.mobile.2 +# application/vnd.doremir.scorecloud-binary-document +application/vnd.dpgraph dpg +application/vnd.dreamfactory dfac +# application/vnd.drive+json +application/vnd.ds-keypoint kpxx +# application/vnd.dtg.local +# application/vnd.dtg.local.flash +# application/vnd.dtg.local.html +application/vnd.dvb.ait ait +# application/vnd.dvb.dvbj +# application/vnd.dvb.esgcontainer +# application/vnd.dvb.ipdcdftnotifaccess +# application/vnd.dvb.ipdcesgaccess +# application/vnd.dvb.ipdcesgaccess2 +# application/vnd.dvb.ipdcesgpdd +# application/vnd.dvb.ipdcroaming +# application/vnd.dvb.iptv.alfec-base +# application/vnd.dvb.iptv.alfec-enhancement +# application/vnd.dvb.notif-aggregate-root+xml +# application/vnd.dvb.notif-container+xml +# application/vnd.dvb.notif-generic+xml +# application/vnd.dvb.notif-ia-msglist+xml +# application/vnd.dvb.notif-ia-registration-request+xml +# application/vnd.dvb.notif-ia-registration-response+xml +# application/vnd.dvb.notif-init+xml +# application/vnd.dvb.pfr +application/vnd.dvb.service svc +# application/vnd.dxr +application/vnd.dynageo geo +# application/vnd.dzr +# application/vnd.easykaraoke.cdgdownload +# application/vnd.ecdis-update +application/vnd.ecowin.chart mag +# application/vnd.ecowin.filerequest +# application/vnd.ecowin.fileupdate +# application/vnd.ecowin.series +# application/vnd.ecowin.seriesrequest +# application/vnd.ecowin.seriesupdate +# application/vnd.emclient.accessrequest+xml +application/vnd.enliven nml +# application/vnd.enphase.envoy +# application/vnd.eprints.data+xml +application/vnd.epson.esf esf +application/vnd.epson.msf msf +application/vnd.epson.quickanime qam +application/vnd.epson.salt slt +application/vnd.epson.ssf ssf +# application/vnd.ericsson.quickcall +application/vnd.eszigno3+xml es3 et3 +# application/vnd.etsi.aoc+xml +# application/vnd.etsi.asic-e+zip +# application/vnd.etsi.asic-s+zip +# application/vnd.etsi.cug+xml +# application/vnd.etsi.iptvcommand+xml +# application/vnd.etsi.iptvdiscovery+xml +# application/vnd.etsi.iptvprofile+xml +# application/vnd.etsi.iptvsad-bc+xml +# application/vnd.etsi.iptvsad-cod+xml +# application/vnd.etsi.iptvsad-npvr+xml +# application/vnd.etsi.iptvservice+xml +# application/vnd.etsi.iptvsync+xml +# application/vnd.etsi.iptvueprofile+xml +# application/vnd.etsi.mcid+xml +# application/vnd.etsi.mheg5 +# application/vnd.etsi.overload-control-policy-dataset+xml +# application/vnd.etsi.pstn+xml +# application/vnd.etsi.sci+xml +# application/vnd.etsi.simservs+xml +# application/vnd.etsi.timestamp-token +# application/vnd.etsi.tsl+xml +# application/vnd.etsi.tsl.der +# application/vnd.eudora.data +application/vnd.ezpix-album ez2 +application/vnd.ezpix-package ez3 +# application/vnd.f-secure.mobile +# application/vnd.fastcopy-disk-image +application/vnd.fdf fdf +application/vnd.fdsn.mseed mseed +application/vnd.fdsn.seed seed dataless +# application/vnd.ffsns +# application/vnd.filmit.zfc +# application/vnd.fints +# application/vnd.firemonkeys.cloudcell +application/vnd.flographit gph +application/vnd.fluxtime.clip ftc +# application/vnd.font-fontforge-sfd +application/vnd.framemaker fm frame maker book +application/vnd.frogans.fnc fnc +application/vnd.frogans.ltf ltf +application/vnd.fsc.weblaunch fsc +application/vnd.fujitsu.oasys oas +application/vnd.fujitsu.oasys2 oa2 +application/vnd.fujitsu.oasys3 oa3 +application/vnd.fujitsu.oasysgp fg5 +application/vnd.fujitsu.oasysprs bh2 +# application/vnd.fujixerox.art-ex +# application/vnd.fujixerox.art4 +application/vnd.fujixerox.ddd ddd +application/vnd.fujixerox.docuworks xdw +application/vnd.fujixerox.docuworks.binder xbd +# application/vnd.fujixerox.docuworks.container +# application/vnd.fujixerox.hbpl +# application/vnd.fut-misnet +application/vnd.fuzzysheet fzs +application/vnd.genomatix.tuxedo txd +# application/vnd.geo+json +# application/vnd.geocube+xml +application/vnd.geogebra.file ggb +application/vnd.geogebra.tool ggt +application/vnd.geometry-explorer gex gre +application/vnd.geonext gxt +application/vnd.geoplan g2w +application/vnd.geospace g3w +# application/vnd.gerber +# application/vnd.globalplatform.card-content-mgt +# application/vnd.globalplatform.card-content-mgt-response +application/vnd.gmx gmx +application/vnd.google-earth.kml+xml kml +application/vnd.google-earth.kmz kmz +# application/vnd.gov.sk.e-form+xml +# application/vnd.gov.sk.e-form+zip +# application/vnd.gov.sk.xmldatacontainer+xml +application/vnd.grafeq gqf gqs +# application/vnd.gridmp +application/vnd.groove-account gac +application/vnd.groove-help ghf +application/vnd.groove-identity-message gim +application/vnd.groove-injector grv +application/vnd.groove-tool-message gtm +application/vnd.groove-tool-template tpl +application/vnd.groove-vcard vcg +# application/vnd.hal+json +application/vnd.hal+xml hal +application/vnd.handheld-entertainment+xml zmm +application/vnd.hbci hbci +# application/vnd.hcl-bireports +# application/vnd.hdt +# application/vnd.heroku+json +application/vnd.hhe.lesson-player les +application/vnd.hp-hpgl hpgl +application/vnd.hp-hpid hpid +application/vnd.hp-hps hps +application/vnd.hp-jlyt jlt +application/vnd.hp-pcl pcl +application/vnd.hp-pclxl pclxl +# application/vnd.httphone +application/vnd.hydrostatix.sof-data sfd-hdstx +# application/vnd.hyperdrive+json +# application/vnd.hzn-3d-crossword +# application/vnd.ibm.afplinedata +# application/vnd.ibm.electronic-media +application/vnd.ibm.minipay mpy +application/vnd.ibm.modcap afp listafp list3820 +application/vnd.ibm.rights-management irm +application/vnd.ibm.secure-container sc +application/vnd.iccprofile icc icm +# application/vnd.ieee.1905 +application/vnd.igloader igl +application/vnd.immervision-ivp ivp +application/vnd.immervision-ivu ivu +# application/vnd.ims.imsccv1p1 +# application/vnd.ims.imsccv1p2 +# application/vnd.ims.imsccv1p3 +# application/vnd.ims.lis.v2.result+json +# application/vnd.ims.lti.v2.toolconsumerprofile+json +# application/vnd.ims.lti.v2.toolproxy+json +# application/vnd.ims.lti.v2.toolproxy.id+json +# application/vnd.ims.lti.v2.toolsettings+json +# application/vnd.ims.lti.v2.toolsettings.simple+json +# application/vnd.informedcontrol.rms+xml +# application/vnd.informix-visionary +# application/vnd.infotech.project +# application/vnd.infotech.project+xml +# application/vnd.innopath.wamp.notification +application/vnd.insors.igm igm +application/vnd.intercon.formnet xpw xpx +application/vnd.intergeo i2g +# application/vnd.intertrust.digibox +# application/vnd.intertrust.nncp +application/vnd.intu.qbo qbo +application/vnd.intu.qfx qfx +# application/vnd.iptc.g2.catalogitem+xml +# application/vnd.iptc.g2.conceptitem+xml +# application/vnd.iptc.g2.knowledgeitem+xml +# application/vnd.iptc.g2.newsitem+xml +# application/vnd.iptc.g2.newsmessage+xml +# application/vnd.iptc.g2.packageitem+xml +# application/vnd.iptc.g2.planningitem+xml +application/vnd.ipunplugged.rcprofile rcprofile +application/vnd.irepository.package+xml irp +application/vnd.is-xpr xpr +application/vnd.isac.fcs fcs +application/vnd.jam jam +# application/vnd.japannet-directory-service +# application/vnd.japannet-jpnstore-wakeup +# application/vnd.japannet-payment-wakeup +# application/vnd.japannet-registration +# application/vnd.japannet-registration-wakeup +# application/vnd.japannet-setstore-wakeup +# application/vnd.japannet-verification +# application/vnd.japannet-verification-wakeup +application/vnd.jcp.javame.midlet-rms rms +application/vnd.jisp jisp +application/vnd.joost.joda-archive joda +# application/vnd.jsk.isdn-ngn +application/vnd.kahootz ktz ktr +application/vnd.kde.karbon karbon +application/vnd.kde.kchart chrt +application/vnd.kde.kformula kfo +application/vnd.kde.kivio flw +application/vnd.kde.kontour kon +application/vnd.kde.kpresenter kpr kpt +application/vnd.kde.kspread ksp +application/vnd.kde.kword kwd kwt +application/vnd.kenameaapp htke +application/vnd.kidspiration kia +application/vnd.kinar kne knp +application/vnd.koan skp skd skt skm +application/vnd.kodak-descriptor sse +application/vnd.las.las+xml lasxml +# application/vnd.liberty-request+xml +application/vnd.llamagraphics.life-balance.desktop lbd +application/vnd.llamagraphics.life-balance.exchange+xml lbe +application/vnd.lotus-1-2-3 123 +application/vnd.lotus-approach apr +application/vnd.lotus-freelance pre +application/vnd.lotus-notes nsf +application/vnd.lotus-organizer org +application/vnd.lotus-screencam scm +application/vnd.lotus-wordpro lwp +application/vnd.macports.portpkg portpkg +# application/vnd.mapbox-vector-tile +# application/vnd.marlin.drm.actiontoken+xml +# application/vnd.marlin.drm.conftoken+xml +# application/vnd.marlin.drm.license+xml +# application/vnd.marlin.drm.mdcf +# application/vnd.mason+json +# application/vnd.maxmind.maxmind-db +application/vnd.mcd mcd +application/vnd.medcalcdata mc1 +application/vnd.mediastation.cdkey cdkey +# application/vnd.meridian-slingshot +application/vnd.mfer mwf +application/vnd.mfmp mfm +# application/vnd.micro+json +application/vnd.micrografx.flo flo +application/vnd.micrografx.igx igx +# application/vnd.microsoft.portable-executable +# application/vnd.miele+json +application/vnd.mif mif +# application/vnd.minisoft-hp3000-save +# application/vnd.mitsubishi.misty-guard.trustweb +application/vnd.mobius.daf daf +application/vnd.mobius.dis dis +application/vnd.mobius.mbk mbk +application/vnd.mobius.mqy mqy +application/vnd.mobius.msl msl +application/vnd.mobius.plc plc +application/vnd.mobius.txf txf +application/vnd.mophun.application mpn +application/vnd.mophun.certificate mpc +# application/vnd.motorola.flexsuite +# application/vnd.motorola.flexsuite.adsi +# application/vnd.motorola.flexsuite.fis +# application/vnd.motorola.flexsuite.gotap +# application/vnd.motorola.flexsuite.kmr +# application/vnd.motorola.flexsuite.ttc +# application/vnd.motorola.flexsuite.wem +# application/vnd.motorola.iprm +application/vnd.mozilla.xul+xml xul +# application/vnd.ms-3mfdocument +application/vnd.ms-artgalry cil +# application/vnd.ms-asf +application/vnd.ms-cab-compressed cab +# application/vnd.ms-color.iccprofile +application/vnd.ms-excel xls xlm xla xlc xlt xlw +application/vnd.ms-excel.addin.macroenabled.12 xlam +application/vnd.ms-excel.sheet.binary.macroenabled.12 xlsb +application/vnd.ms-excel.sheet.macroenabled.12 xlsm +application/vnd.ms-excel.template.macroenabled.12 xltm +application/vnd.ms-fontobject eot +application/vnd.ms-htmlhelp chm +application/vnd.ms-ims ims +application/vnd.ms-lrm lrm +# application/vnd.ms-office.activex+xml +application/vnd.ms-officetheme thmx +# application/vnd.ms-opentype +# application/vnd.ms-package.obfuscated-opentype +application/vnd.ms-pki.seccat cat +application/vnd.ms-pki.stl stl +# application/vnd.ms-playready.initiator+xml +application/vnd.ms-powerpoint ppt pps pot +application/vnd.ms-powerpoint.addin.macroenabled.12 ppam +application/vnd.ms-powerpoint.presentation.macroenabled.12 pptm +application/vnd.ms-powerpoint.slide.macroenabled.12 sldm +application/vnd.ms-powerpoint.slideshow.macroenabled.12 ppsm +application/vnd.ms-powerpoint.template.macroenabled.12 potm +# application/vnd.ms-printdevicecapabilities+xml +# application/vnd.ms-printing.printticket+xml +# application/vnd.ms-printschematicket+xml +application/vnd.ms-project mpp mpt +# application/vnd.ms-tnef +# application/vnd.ms-windows.devicepairing +# application/vnd.ms-windows.nwprinting.oob +# application/vnd.ms-windows.printerpairing +# application/vnd.ms-windows.wsd.oob +# application/vnd.ms-wmdrm.lic-chlg-req +# application/vnd.ms-wmdrm.lic-resp +# application/vnd.ms-wmdrm.meter-chlg-req +# application/vnd.ms-wmdrm.meter-resp +application/vnd.ms-word.document.macroenabled.12 docm +application/vnd.ms-word.template.macroenabled.12 dotm +application/vnd.ms-works wps wks wcm wdb +application/vnd.ms-wpl wpl +application/vnd.ms-xpsdocument xps +# application/vnd.msa-disk-image +application/vnd.mseq mseq +# application/vnd.msign +# application/vnd.multiad.creator +# application/vnd.multiad.creator.cif +# application/vnd.music-niff +application/vnd.musician mus +application/vnd.muvee.style msty +application/vnd.mynfc taglet +# application/vnd.ncd.control +# application/vnd.ncd.reference +# application/vnd.nervana +# application/vnd.netfpx +application/vnd.neurolanguage.nlu nlu +# application/vnd.nintendo.nitro.rom +# application/vnd.nintendo.snes.rom +application/vnd.nitf ntf nitf +application/vnd.noblenet-directory nnd +application/vnd.noblenet-sealer nns +application/vnd.noblenet-web nnw +# application/vnd.nokia.catalogs +# application/vnd.nokia.conml+wbxml +# application/vnd.nokia.conml+xml +# application/vnd.nokia.iptv.config+xml +# application/vnd.nokia.isds-radio-presets +# application/vnd.nokia.landmark+wbxml +# application/vnd.nokia.landmark+xml +# application/vnd.nokia.landmarkcollection+xml +# application/vnd.nokia.n-gage.ac+xml +application/vnd.nokia.n-gage.data ngdat +application/vnd.nokia.n-gage.symbian.install n-gage +# application/vnd.nokia.ncd +# application/vnd.nokia.pcd+wbxml +# application/vnd.nokia.pcd+xml +application/vnd.nokia.radio-preset rpst +application/vnd.nokia.radio-presets rpss +application/vnd.novadigm.edm edm +application/vnd.novadigm.edx edx +application/vnd.novadigm.ext ext +# application/vnd.ntt-local.content-share +# application/vnd.ntt-local.file-transfer +# application/vnd.ntt-local.ogw_remote-access +# application/vnd.ntt-local.sip-ta_remote +# application/vnd.ntt-local.sip-ta_tcp_stream +application/vnd.oasis.opendocument.chart odc +application/vnd.oasis.opendocument.chart-template otc +application/vnd.oasis.opendocument.database odb +application/vnd.oasis.opendocument.formula odf +application/vnd.oasis.opendocument.formula-template odft +application/vnd.oasis.opendocument.graphics odg +application/vnd.oasis.opendocument.graphics-template otg +application/vnd.oasis.opendocument.image odi +application/vnd.oasis.opendocument.image-template oti +application/vnd.oasis.opendocument.presentation odp +application/vnd.oasis.opendocument.presentation-template otp +application/vnd.oasis.opendocument.spreadsheet ods +application/vnd.oasis.opendocument.spreadsheet-template ots +application/vnd.oasis.opendocument.text odt +application/vnd.oasis.opendocument.text-master odm +application/vnd.oasis.opendocument.text-template ott +application/vnd.oasis.opendocument.text-web oth +# application/vnd.obn +# application/vnd.oftn.l10n+json +# application/vnd.oipf.contentaccessdownload+xml +# application/vnd.oipf.contentaccessstreaming+xml +# application/vnd.oipf.cspg-hexbinary +# application/vnd.oipf.dae.svg+xml +# application/vnd.oipf.dae.xhtml+xml +# application/vnd.oipf.mippvcontrolmessage+xml +# application/vnd.oipf.pae.gem +# application/vnd.oipf.spdiscovery+xml +# application/vnd.oipf.spdlist+xml +# application/vnd.oipf.ueprofile+xml +# application/vnd.oipf.userprofile+xml +application/vnd.olpc-sugar xo +# application/vnd.oma-scws-config +# application/vnd.oma-scws-http-request +# application/vnd.oma-scws-http-response +# application/vnd.oma.bcast.associated-procedure-parameter+xml +# application/vnd.oma.bcast.drm-trigger+xml +# application/vnd.oma.bcast.imd+xml +# application/vnd.oma.bcast.ltkm +# application/vnd.oma.bcast.notification+xml +# application/vnd.oma.bcast.provisioningtrigger +# application/vnd.oma.bcast.sgboot +# application/vnd.oma.bcast.sgdd+xml +# application/vnd.oma.bcast.sgdu +# application/vnd.oma.bcast.simple-symbol-container +# application/vnd.oma.bcast.smartcard-trigger+xml +# application/vnd.oma.bcast.sprov+xml +# application/vnd.oma.bcast.stkm +# application/vnd.oma.cab-address-book+xml +# application/vnd.oma.cab-feature-handler+xml +# application/vnd.oma.cab-pcc+xml +# application/vnd.oma.cab-subs-invite+xml +# application/vnd.oma.cab-user-prefs+xml +# application/vnd.oma.dcd +# application/vnd.oma.dcdc +application/vnd.oma.dd2+xml dd2 +# application/vnd.oma.drm.risd+xml +# application/vnd.oma.group-usage-list+xml +# application/vnd.oma.pal+xml +# application/vnd.oma.poc.detailed-progress-report+xml +# application/vnd.oma.poc.final-report+xml +# application/vnd.oma.poc.groups+xml +# application/vnd.oma.poc.invocation-descriptor+xml +# application/vnd.oma.poc.optimized-progress-report+xml +# application/vnd.oma.push +# application/vnd.oma.scidm.messages+xml +# application/vnd.oma.xcap-directory+xml +# application/vnd.omads-email+xml +# application/vnd.omads-file+xml +# application/vnd.omads-folder+xml +# application/vnd.omaloc-supl-init +# application/vnd.openblox.game+xml +# application/vnd.openblox.game-binary +# application/vnd.openeye.oeb +application/vnd.openofficeorg.extension oxt +# application/vnd.openxmlformats-officedocument.custom-properties+xml +# application/vnd.openxmlformats-officedocument.customxmlproperties+xml +# application/vnd.openxmlformats-officedocument.drawing+xml +# application/vnd.openxmlformats-officedocument.drawingml.chart+xml +# application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramcolors+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramdata+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramlayout+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramstyle+xml +# application/vnd.openxmlformats-officedocument.extended-properties+xml +# application/vnd.openxmlformats-officedocument.presentationml.commentauthors+xml +# application/vnd.openxmlformats-officedocument.presentationml.comments+xml +# application/vnd.openxmlformats-officedocument.presentationml.handoutmaster+xml +# application/vnd.openxmlformats-officedocument.presentationml.notesmaster+xml +# application/vnd.openxmlformats-officedocument.presentationml.notesslide+xml +application/vnd.openxmlformats-officedocument.presentationml.presentation pptx +# application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml +# application/vnd.openxmlformats-officedocument.presentationml.presprops+xml +application/vnd.openxmlformats-officedocument.presentationml.slide sldx +# application/vnd.openxmlformats-officedocument.presentationml.slide+xml +# application/vnd.openxmlformats-officedocument.presentationml.slidelayout+xml +# application/vnd.openxmlformats-officedocument.presentationml.slidemaster+xml +application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx +# application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml +# application/vnd.openxmlformats-officedocument.presentationml.slideupdateinfo+xml +# application/vnd.openxmlformats-officedocument.presentationml.tablestyles+xml +# application/vnd.openxmlformats-officedocument.presentationml.tags+xml +application/vnd.openxmlformats-officedocument.presentationml.template potx +# application/vnd.openxmlformats-officedocument.presentationml.template.main+xml +# application/vnd.openxmlformats-officedocument.presentationml.viewprops+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.calcchain+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.externallink+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcachedefinition+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcacherecords+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.pivottable+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.querytable+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionheaders+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionlog+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.sharedstrings+xml +application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx +# application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.sheetmetadata+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.tablesinglecells+xml +application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx +# application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.usernames+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.volatiledependencies+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml +# application/vnd.openxmlformats-officedocument.theme+xml +# application/vnd.openxmlformats-officedocument.themeoverride+xml +# application/vnd.openxmlformats-officedocument.vmldrawing +# application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml +application/vnd.openxmlformats-officedocument.wordprocessingml.document docx +# application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.fonttable+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml +application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx +# application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.websettings+xml +# application/vnd.openxmlformats-package.core-properties+xml +# application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml +# application/vnd.openxmlformats-package.relationships+xml +# application/vnd.oracle.resource+json +# application/vnd.orange.indata +# application/vnd.osa.netdeploy +application/vnd.osgeo.mapguide.package mgp +# application/vnd.osgi.bundle +application/vnd.osgi.dp dp +application/vnd.osgi.subsystem esa +# application/vnd.otps.ct-kip+xml +# application/vnd.oxli.countgraph +# application/vnd.pagerduty+json +application/vnd.palm pdb pqa oprc +# application/vnd.panoply +# application/vnd.paos.xml +application/vnd.pawaafile paw +# application/vnd.pcos +application/vnd.pg.format str +application/vnd.pg.osasli ei6 +# application/vnd.piaccess.application-licence +application/vnd.picsel efif +application/vnd.pmi.widget wg +# application/vnd.poc.group-advertisement+xml +application/vnd.pocketlearn plf +application/vnd.powerbuilder6 pbd +# application/vnd.powerbuilder6-s +# application/vnd.powerbuilder7 +# application/vnd.powerbuilder7-s +# application/vnd.powerbuilder75 +# application/vnd.powerbuilder75-s +# application/vnd.preminet +application/vnd.previewsystems.box box +application/vnd.proteus.magazine mgz +application/vnd.publishare-delta-tree qps +application/vnd.pvi.ptid1 ptid +# application/vnd.pwg-multiplexed +# application/vnd.pwg-xhtml-print+xml +# application/vnd.qualcomm.brew-app-res +application/vnd.quark.quarkxpress qxd qxt qwd qwt qxl qxb +# application/vnd.quobject-quoxdocument +# application/vnd.radisys.moml+xml +# application/vnd.radisys.msml+xml +# application/vnd.radisys.msml-audit+xml +# application/vnd.radisys.msml-audit-conf+xml +# application/vnd.radisys.msml-audit-conn+xml +# application/vnd.radisys.msml-audit-dialog+xml +# application/vnd.radisys.msml-audit-stream+xml +# application/vnd.radisys.msml-conf+xml +# application/vnd.radisys.msml-dialog+xml +# application/vnd.radisys.msml-dialog-base+xml +# application/vnd.radisys.msml-dialog-fax-detect+xml +# application/vnd.radisys.msml-dialog-fax-sendrecv+xml +# application/vnd.radisys.msml-dialog-group+xml +# application/vnd.radisys.msml-dialog-speech+xml +# application/vnd.radisys.msml-dialog-transform+xml +# application/vnd.rainstor.data +# application/vnd.rapid +application/vnd.realvnc.bed bed +application/vnd.recordare.musicxml mxl +application/vnd.recordare.musicxml+xml musicxml +# application/vnd.renlearn.rlprint +application/vnd.rig.cryptonote cryptonote +application/vnd.rim.cod cod +application/vnd.rn-realmedia rm +application/vnd.rn-realmedia-vbr rmvb +application/vnd.route66.link66+xml link66 +# application/vnd.rs-274x +# application/vnd.ruckus.download +# application/vnd.s3sms +application/vnd.sailingtracker.track st +# application/vnd.sbm.cid +# application/vnd.sbm.mid2 +# application/vnd.scribus +# application/vnd.sealed.3df +# application/vnd.sealed.csf +# application/vnd.sealed.doc +# application/vnd.sealed.eml +# application/vnd.sealed.mht +# application/vnd.sealed.net +# application/vnd.sealed.ppt +# application/vnd.sealed.tiff +# application/vnd.sealed.xls +# application/vnd.sealedmedia.softseal.html +# application/vnd.sealedmedia.softseal.pdf +application/vnd.seemail see +application/vnd.sema sema +application/vnd.semd semd +application/vnd.semf semf +application/vnd.shana.informed.formdata ifm +application/vnd.shana.informed.formtemplate itp +application/vnd.shana.informed.interchange iif +application/vnd.shana.informed.package ipk +application/vnd.simtech-mindmapper twd twds +# application/vnd.siren+json +application/vnd.smaf mmf +# application/vnd.smart.notebook +application/vnd.smart.teacher teacher +# application/vnd.software602.filler.form+xml +# application/vnd.software602.filler.form-xml-zip +application/vnd.solent.sdkm+xml sdkm sdkd +application/vnd.spotfire.dxp dxp +application/vnd.spotfire.sfs sfs +# application/vnd.sss-cod +# application/vnd.sss-dtf +# application/vnd.sss-ntf +application/vnd.stardivision.calc sdc +application/vnd.stardivision.draw sda +application/vnd.stardivision.impress sdd +application/vnd.stardivision.math smf +application/vnd.stardivision.writer sdw vor +application/vnd.stardivision.writer-global sgl +application/vnd.stepmania.package smzip +application/vnd.stepmania.stepchart sm +# application/vnd.street-stream +# application/vnd.sun.wadl+xml +application/vnd.sun.xml.calc sxc +application/vnd.sun.xml.calc.template stc +application/vnd.sun.xml.draw sxd +application/vnd.sun.xml.draw.template std +application/vnd.sun.xml.impress sxi +application/vnd.sun.xml.impress.template sti +application/vnd.sun.xml.math sxm +application/vnd.sun.xml.writer sxw +application/vnd.sun.xml.writer.global sxg +application/vnd.sun.xml.writer.template stw +application/vnd.sus-calendar sus susp +application/vnd.svd svd +# application/vnd.swiftview-ics +application/vnd.symbian.install sis sisx +application/vnd.syncml+xml xsm +application/vnd.syncml.dm+wbxml bdm +application/vnd.syncml.dm+xml xdm +# application/vnd.syncml.dm.notification +# application/vnd.syncml.dmddf+wbxml +# application/vnd.syncml.dmddf+xml +# application/vnd.syncml.dmtnds+wbxml +# application/vnd.syncml.dmtnds+xml +# application/vnd.syncml.ds.notification +application/vnd.tao.intent-module-archive tao +application/vnd.tcpdump.pcap pcap cap dmp +# application/vnd.tmd.mediaflex.api+xml +# application/vnd.tml +application/vnd.tmobile-livetv tmo +application/vnd.trid.tpt tpt +application/vnd.triscape.mxs mxs +application/vnd.trueapp tra +# application/vnd.truedoc +# application/vnd.ubisoft.webplayer +application/vnd.ufdl ufd ufdl +application/vnd.uiq.theme utz +application/vnd.umajin umj +application/vnd.unity unityweb +application/vnd.uoml+xml uoml +# application/vnd.uplanet.alert +# application/vnd.uplanet.alert-wbxml +# application/vnd.uplanet.bearer-choice +# application/vnd.uplanet.bearer-choice-wbxml +# application/vnd.uplanet.cacheop +# application/vnd.uplanet.cacheop-wbxml +# application/vnd.uplanet.channel +# application/vnd.uplanet.channel-wbxml +# application/vnd.uplanet.list +# application/vnd.uplanet.list-wbxml +# application/vnd.uplanet.listcmd +# application/vnd.uplanet.listcmd-wbxml +# application/vnd.uplanet.signal +# application/vnd.uri-map +# application/vnd.valve.source.material +application/vnd.vcx vcx +# application/vnd.vd-study +# application/vnd.vectorworks +# application/vnd.verimatrix.vcas +# application/vnd.vidsoft.vidconference +application/vnd.visio vsd vst vss vsw +application/vnd.visionary vis +# application/vnd.vividence.scriptfile +application/vnd.vsf vsf +# application/vnd.wap.sic +# application/vnd.wap.slc +application/vnd.wap.wbxml wbxml +application/vnd.wap.wmlc wmlc +application/vnd.wap.wmlscriptc wmlsc +application/vnd.webturbo wtb +# application/vnd.wfa.p2p +# application/vnd.wfa.wsc +# application/vnd.windows.devicepairing +# application/vnd.wmc +# application/vnd.wmf.bootstrap +# application/vnd.wolfram.mathematica +# application/vnd.wolfram.mathematica.package +application/vnd.wolfram.player nbp +application/vnd.wordperfect wpd +application/vnd.wqd wqd +# application/vnd.wrq-hp3000-labelled +application/vnd.wt.stf stf +# application/vnd.wv.csp+wbxml +# application/vnd.wv.csp+xml +# application/vnd.wv.ssp+xml +# application/vnd.xacml+json +application/vnd.xara xar +application/vnd.xfdl xfdl +# application/vnd.xfdl.webform +# application/vnd.xmi+xml +# application/vnd.xmpie.cpkg +# application/vnd.xmpie.dpkg +# application/vnd.xmpie.plan +# application/vnd.xmpie.ppkg +# application/vnd.xmpie.xlim +application/vnd.yamaha.hv-dic hvd +application/vnd.yamaha.hv-script hvs +application/vnd.yamaha.hv-voice hvp +application/vnd.yamaha.openscoreformat osf +application/vnd.yamaha.openscoreformat.osfpvg+xml osfpvg +# application/vnd.yamaha.remote-setup +application/vnd.yamaha.smaf-audio saf +application/vnd.yamaha.smaf-phrase spf +# application/vnd.yamaha.through-ngn +# application/vnd.yamaha.tunnel-udpencap +# application/vnd.yaoweme +application/vnd.yellowriver-custom-menu cmp +application/vnd.zul zir zirz +application/vnd.zzazz.deck+xml zaz +application/voicexml+xml vxml +# application/vq-rtcpxr +# application/watcherinfo+xml +# application/whoispp-query +# application/whoispp-response +application/widget wgt +application/winhlp hlp +# application/wita +# application/wordperfect5.1 +application/wsdl+xml wsdl +application/wspolicy+xml wspolicy +application/x-7z-compressed 7z +application/x-abiword abw +application/x-ace-compressed ace +# application/x-amf +application/x-apple-diskimage dmg +application/x-authorware-bin aab x32 u32 vox +application/x-authorware-map aam +application/x-authorware-seg aas +application/x-bcpio bcpio +application/x-bittorrent torrent +application/x-blorb blb blorb +application/x-bzip bz +application/x-bzip2 bz2 boz +application/x-cbr cbr cba cbt cbz cb7 +application/x-cdlink vcd +application/x-cfs-compressed cfs +application/x-chat chat +application/x-chess-pgn pgn +# application/x-compress +application/x-conference nsc +application/x-cpio cpio +application/x-csh csh +application/x-debian-package deb udeb +application/x-dgc-compressed dgc +application/x-director dir dcr dxr cst cct cxt w3d fgd swa +application/x-doom wad +application/x-dtbncx+xml ncx +application/x-dtbook+xml dtb +application/x-dtbresource+xml res +application/x-dvi dvi +application/x-envoy evy +application/x-eva eva +application/x-font-bdf bdf +# application/x-font-dos +# application/x-font-framemaker +application/x-font-ghostscript gsf +# application/x-font-libgrx +application/x-font-linux-psf psf +application/x-font-otf otf +application/x-font-pcf pcf +application/x-font-snf snf +# application/x-font-speedo +# application/x-font-sunos-news +application/x-font-ttf ttf ttc +application/x-font-type1 pfa pfb pfm afm +# application/x-font-vfont +application/x-freearc arc +application/x-futuresplash spl +application/x-gca-compressed gca +application/x-glulx ulx +application/x-gnumeric gnumeric +application/x-gramps-xml gramps +application/x-gtar gtar +# application/x-gzip +application/x-hdf hdf +application/x-install-instructions install +application/x-iso9660-image iso +application/x-java-jnlp-file jnlp +application/x-latex latex +application/x-lzh-compressed lzh lha +application/x-mie mie +application/x-mobipocket-ebook prc mobi +application/x-ms-application application +application/x-ms-shortcut lnk +application/x-ms-wmd wmd +application/x-ms-wmz wmz +application/x-ms-xbap xbap +application/x-msaccess mdb +application/x-msbinder obd +application/x-mscardfile crd +application/x-msclip clp +application/x-msdownload exe dll com bat msi +application/x-msmediaview mvb m13 m14 +application/x-msmetafile wmf wmz emf emz +application/x-msmoney mny +application/x-mspublisher pub +application/x-msschedule scd +application/x-msterminal trm +application/x-mswrite wri +application/x-netcdf nc cdf +application/x-nzb nzb +application/x-pkcs12 p12 pfx +application/x-pkcs7-certificates p7b spc +application/x-pkcs7-certreqresp p7r +application/x-rar-compressed rar +application/x-research-info-systems ris +application/x-sh sh +application/x-shar shar +application/x-shockwave-flash swf +application/x-silverlight-app xap +application/x-sql sql +application/x-stuffit sit +application/x-stuffitx sitx +application/x-subrip srt +application/x-sv4cpio sv4cpio +application/x-sv4crc sv4crc +application/x-t3vm-image t3 +application/x-tads gam +application/x-tar tar +application/x-tcl tcl +application/x-tex tex +application/x-tex-tfm tfm +application/x-texinfo texinfo texi +application/x-tgif obj +application/x-ustar ustar +application/x-wais-source src +# application/x-www-form-urlencoded +application/x-x509-ca-cert der crt +application/x-xfig fig +application/x-xliff+xml xlf +application/x-xpinstall xpi +application/x-xz xz +application/x-zmachine z1 z2 z3 z4 z5 z6 z7 z8 +# application/x400-bp +# application/xacml+xml +application/xaml+xml xaml +# application/xcap-att+xml +# application/xcap-caps+xml +application/xcap-diff+xml xdf +# application/xcap-el+xml +# application/xcap-error+xml +# application/xcap-ns+xml +# application/xcon-conference-info+xml +# application/xcon-conference-info-diff+xml +application/xenc+xml xenc +application/xhtml+xml xhtml xht +# application/xhtml-voice+xml +application/xml xml xsl +application/xml-dtd dtd +# application/xml-external-parsed-entity +# application/xml-patch+xml +# application/xmpp+xml +application/xop+xml xop +application/xproc+xml xpl +application/xslt+xml xslt +application/xspf+xml xspf +application/xv+xml mxml xhvml xvml xvm +application/yang yang +application/yin+xml yin +application/zip zip +# application/zlib +# audio/1d-interleaved-parityfec +# audio/32kadpcm +# audio/3gpp +# audio/3gpp2 +# audio/ac3 +audio/adpcm adp +# audio/amr +# audio/amr-wb +# audio/amr-wb+ +# audio/aptx +# audio/asc +# audio/atrac-advanced-lossless +# audio/atrac-x +# audio/atrac3 +audio/basic au snd +# audio/bv16 +# audio/bv32 +# audio/clearmode +# audio/cn +# audio/dat12 +# audio/dls +# audio/dsr-es201108 +# audio/dsr-es202050 +# audio/dsr-es202211 +# audio/dsr-es202212 +# audio/dv +# audio/dvi4 +# audio/eac3 +# audio/encaprtp +# audio/evrc +# audio/evrc-qcp +# audio/evrc0 +# audio/evrc1 +# audio/evrcb +# audio/evrcb0 +# audio/evrcb1 +# audio/evrcnw +# audio/evrcnw0 +# audio/evrcnw1 +# audio/evrcwb +# audio/evrcwb0 +# audio/evrcwb1 +# audio/evs +# audio/example +# audio/fwdred +# audio/g711-0 +# audio/g719 +# audio/g722 +# audio/g7221 +# audio/g723 +# audio/g726-16 +# audio/g726-24 +# audio/g726-32 +# audio/g726-40 +# audio/g728 +# audio/g729 +# audio/g7291 +# audio/g729d +# audio/g729e +# audio/gsm +# audio/gsm-efr +# audio/gsm-hr-08 +# audio/ilbc +# audio/ip-mr_v2.5 +# audio/isac +# audio/l16 +# audio/l20 +# audio/l24 +# audio/l8 +# audio/lpc +audio/midi mid midi kar rmi +# audio/mobile-xmf +audio/mp4 m4a mp4a +# audio/mp4a-latm +# audio/mpa +# audio/mpa-robust +audio/mpeg mpga mp2 mp2a mp3 m2a m3a +# audio/mpeg4-generic +# audio/musepack +audio/ogg oga ogg spx +# audio/opus +# audio/parityfec +# audio/pcma +# audio/pcma-wb +# audio/pcmu +# audio/pcmu-wb +# audio/prs.sid +# audio/qcelp +# audio/raptorfec +# audio/red +# audio/rtp-enc-aescm128 +# audio/rtp-midi +# audio/rtploopback +# audio/rtx +audio/s3m s3m +audio/silk sil +# audio/smv +# audio/smv-qcp +# audio/smv0 +# audio/sp-midi +# audio/speex +# audio/t140c +# audio/t38 +# audio/telephone-event +# audio/tone +# audio/uemclip +# audio/ulpfec +# audio/vdvi +# audio/vmr-wb +# audio/vnd.3gpp.iufp +# audio/vnd.4sb +# audio/vnd.audiokoz +# audio/vnd.celp +# audio/vnd.cisco.nse +# audio/vnd.cmles.radio-events +# audio/vnd.cns.anp1 +# audio/vnd.cns.inf1 +audio/vnd.dece.audio uva uvva +audio/vnd.digital-winds eol +# audio/vnd.dlna.adts +# audio/vnd.dolby.heaac.1 +# audio/vnd.dolby.heaac.2 +# audio/vnd.dolby.mlp +# audio/vnd.dolby.mps +# audio/vnd.dolby.pl2 +# audio/vnd.dolby.pl2x +# audio/vnd.dolby.pl2z +# audio/vnd.dolby.pulse.1 +audio/vnd.dra dra +audio/vnd.dts dts +audio/vnd.dts.hd dtshd +# audio/vnd.dvb.file +# audio/vnd.everad.plj +# audio/vnd.hns.audio +audio/vnd.lucent.voice lvp +audio/vnd.ms-playready.media.pya pya +# audio/vnd.nokia.mobile-xmf +# audio/vnd.nortel.vbk +audio/vnd.nuera.ecelp4800 ecelp4800 +audio/vnd.nuera.ecelp7470 ecelp7470 +audio/vnd.nuera.ecelp9600 ecelp9600 +# audio/vnd.octel.sbc +# audio/vnd.qcelp +# audio/vnd.rhetorex.32kadpcm +audio/vnd.rip rip +# audio/vnd.sealedmedia.softseal.mpeg +# audio/vnd.vmx.cvsd +# audio/vorbis +# audio/vorbis-config +audio/webm weba +audio/x-aac aac +audio/x-aiff aif aiff aifc +audio/x-caf caf +audio/x-flac flac +audio/x-matroska mka +audio/x-mpegurl m3u +audio/x-ms-wax wax +audio/x-ms-wma wma +audio/x-pn-realaudio ram ra +audio/x-pn-realaudio-plugin rmp +# audio/x-tta +audio/x-wav wav +audio/xm xm +chemical/x-cdx cdx +chemical/x-cif cif +chemical/x-cmdf cmdf +chemical/x-cml cml +chemical/x-csml csml +# chemical/x-pdb +chemical/x-xyz xyz +image/bmp bmp +image/cgm cgm +# image/example +# image/fits +image/g3fax g3 +image/gif gif +image/ief ief +# image/jp2 +image/jpeg jpeg jpg jpe +# image/jpm +# image/jpx +image/ktx ktx +# image/naplps +image/png png +image/prs.btif btif +# image/prs.pti +# image/pwg-raster +image/sgi sgi +image/svg+xml svg svgz +# image/t38 +image/tiff tiff tif +# image/tiff-fx +image/vnd.adobe.photoshop psd +# image/vnd.airzip.accelerator.azv +# image/vnd.cns.inf2 +image/vnd.dece.graphic uvi uvvi uvg uvvg +image/vnd.djvu djvu djv +image/vnd.dvb.subtitle sub +image/vnd.dwg dwg +image/vnd.dxf dxf +image/vnd.fastbidsheet fbs +image/vnd.fpx fpx +image/vnd.fst fst +image/vnd.fujixerox.edmics-mmr mmr +image/vnd.fujixerox.edmics-rlc rlc +# image/vnd.globalgraphics.pgb +# image/vnd.microsoft.icon +# image/vnd.mix +# image/vnd.mozilla.apng +image/vnd.ms-modi mdi +image/vnd.ms-photo wdp +image/vnd.net-fpx npx +# image/vnd.radiance +# image/vnd.sealed.png +# image/vnd.sealedmedia.softseal.gif +# image/vnd.sealedmedia.softseal.jpg +# image/vnd.svf +# image/vnd.tencent.tap +# image/vnd.valve.source.texture +image/vnd.wap.wbmp wbmp +image/vnd.xiff xif +# image/vnd.zbrush.pcx +image/webp webp +image/x-3ds 3ds +image/x-cmu-raster ras +image/x-cmx cmx +image/x-freehand fh fhc fh4 fh5 fh7 +image/x-icon ico +image/x-mrsid-image sid +image/x-pcx pcx +image/x-pict pic pct +image/x-portable-anymap pnm +image/x-portable-bitmap pbm +image/x-portable-graymap pgm +image/x-portable-pixmap ppm +image/x-rgb rgb +image/x-tga tga +image/x-xbitmap xbm +image/x-xpixmap xpm +image/x-xwindowdump xwd +# message/cpim +# message/delivery-status +# message/disposition-notification +# message/example +# message/external-body +# message/feedback-report +# message/global +# message/global-delivery-status +# message/global-disposition-notification +# message/global-headers +# message/http +# message/imdn+xml +# message/news +# message/partial +message/rfc822 eml mime +# message/s-http +# message/sip +# message/sipfrag +# message/tracking-status +# message/vnd.si.simp +# message/vnd.wfa.wsc +# model/example +model/iges igs iges +model/mesh msh mesh silo +model/vnd.collada+xml dae +model/vnd.dwf dwf +# model/vnd.flatland.3dml +model/vnd.gdl gdl +# model/vnd.gs-gdl +# model/vnd.gs.gdl +model/vnd.gtw gtw +# model/vnd.moml+xml +model/vnd.mts mts +# model/vnd.opengex +# model/vnd.parasolid.transmit.binary +# model/vnd.parasolid.transmit.text +# model/vnd.rosette.annotated-data-model +# model/vnd.valve.source.compiled-map +model/vnd.vtu vtu +model/vrml wrl vrml +model/x3d+binary x3db x3dbz +# model/x3d+fastinfoset +model/x3d+vrml x3dv x3dvz +model/x3d+xml x3d x3dz +# model/x3d-vrml +# multipart/alternative +# multipart/appledouble +# multipart/byteranges +# multipart/digest +# multipart/encrypted +# multipart/example +# multipart/form-data +# multipart/header-set +# multipart/mixed +# multipart/parallel +# multipart/related +# multipart/report +# multipart/signed +# multipart/voice-message +# multipart/x-mixed-replace +# text/1d-interleaved-parityfec +text/cache-manifest appcache +text/calendar ics ifb +text/css css +text/csv csv +# text/csv-schema +# text/directory +# text/dns +# text/ecmascript +# text/encaprtp +# text/enriched +# text/example +# text/fwdred +# text/grammar-ref-list +text/html html htm +# text/javascript +# text/jcr-cnd +# text/markdown +# text/mizar +text/n3 n3 +# text/parameters +# text/parityfec +text/plain txt text conf def list log in +# text/provenance-notation +# text/prs.fallenstein.rst +text/prs.lines.tag dsc +# text/raptorfec +# text/red +# text/rfc822-headers +text/richtext rtx +# text/rtf +# text/rtp-enc-aescm128 +# text/rtploopback +# text/rtx +text/sgml sgml sgm +# text/t140 +text/tab-separated-values tsv +text/troff t tr roff man me ms +text/turtle ttl +# text/ulpfec +text/uri-list uri uris urls +text/vcard vcard +# text/vnd.a +# text/vnd.abc +text/vnd.curl curl +text/vnd.curl.dcurl dcurl +text/vnd.curl.mcurl mcurl +text/vnd.curl.scurl scurl +# text/vnd.debian.copyright +# text/vnd.dmclientscript +text/vnd.dvb.subtitle sub +# text/vnd.esmertec.theme-descriptor +text/vnd.fly fly +text/vnd.fmi.flexstor flx +text/vnd.graphviz gv +text/vnd.in3d.3dml 3dml +text/vnd.in3d.spot spot +# text/vnd.iptc.newsml +# text/vnd.iptc.nitf +# text/vnd.latex-z +# text/vnd.motorola.reflex +# text/vnd.ms-mediapackage +# text/vnd.net2phone.commcenter.command +# text/vnd.radisys.msml-basic-layout +# text/vnd.si.uricatalogue +text/vnd.sun.j2me.app-descriptor jad +# text/vnd.trolltech.linguist +# text/vnd.wap.si +# text/vnd.wap.sl +text/vnd.wap.wml wml +text/vnd.wap.wmlscript wmls +text/x-asm s asm +text/x-c c cc cxx cpp h hh dic +text/x-fortran f for f77 f90 +text/x-java-source java +text/x-nfo nfo +text/x-opml opml +text/x-pascal p pas +text/x-setext etx +text/x-sfv sfv +text/x-uuencode uu +text/x-vcalendar vcs +text/x-vcard vcf +# text/xml +# text/xml-external-parsed-entity +# video/1d-interleaved-parityfec +video/3gpp 3gp +# video/3gpp-tt +video/3gpp2 3g2 +# video/bmpeg +# video/bt656 +# video/celb +# video/dv +# video/encaprtp +# video/example +video/h261 h261 +video/h263 h263 +# video/h263-1998 +# video/h263-2000 +video/h264 h264 +# video/h264-rcdo +# video/h264-svc +# video/h265 +# video/iso.segment +video/jpeg jpgv +# video/jpeg2000 +video/jpm jpm jpgm +video/mj2 mj2 mjp2 +# video/mp1s +# video/mp2p +# video/mp2t +video/mp4 mp4 mp4v mpg4 +# video/mp4v-es +video/mpeg mpeg mpg mpe m1v m2v +# video/mpeg4-generic +# video/mpv +# video/nv +video/ogg ogv +# video/parityfec +# video/pointer +video/quicktime qt mov +# video/raptorfec +# video/raw +# video/rtp-enc-aescm128 +# video/rtploopback +# video/rtx +# video/smpte292m +# video/ulpfec +# video/vc1 +# video/vnd.cctv +video/vnd.dece.hd uvh uvvh +video/vnd.dece.mobile uvm uvvm +# video/vnd.dece.mp4 +video/vnd.dece.pd uvp uvvp +video/vnd.dece.sd uvs uvvs +video/vnd.dece.video uvv uvvv +# video/vnd.directv.mpeg +# video/vnd.directv.mpeg-tts +# video/vnd.dlna.mpeg-tts +video/vnd.dvb.file dvb +video/vnd.fvt fvt +# video/vnd.hns.video +# video/vnd.iptvforum.1dparityfec-1010 +# video/vnd.iptvforum.1dparityfec-2005 +# video/vnd.iptvforum.2dparityfec-1010 +# video/vnd.iptvforum.2dparityfec-2005 +# video/vnd.iptvforum.ttsavc +# video/vnd.iptvforum.ttsmpeg2 +# video/vnd.motorola.video +# video/vnd.motorola.videop +video/vnd.mpegurl mxu m4u +video/vnd.ms-playready.media.pyv pyv +# video/vnd.nokia.interleaved-multimedia +# video/vnd.nokia.videovoip +# video/vnd.objectvideo +# video/vnd.radgamettools.bink +# video/vnd.radgamettools.smacker +# video/vnd.sealed.mpeg1 +# video/vnd.sealed.mpeg4 +# video/vnd.sealed.swf +# video/vnd.sealedmedia.softseal.mov +video/vnd.uvvu.mp4 uvu uvvu +video/vnd.vivo viv +# video/vp8 +video/webm webm +video/x-f4v f4v +video/x-fli fli +video/x-flv flv +video/x-m4v m4v +video/x-matroska mkv mk3d mks +video/x-mng mng +video/x-ms-asf asf asx +video/x-ms-vob vob +video/x-ms-wm wm +video/x-ms-wmv wmv +video/x-ms-wmx wmx +video/x-ms-wvx wvx +video/x-msvideo avi +video/x-sgi-movie movie +video/x-smv smv +x-conference/x-cooltalk ice diff --git a/client/src/test/java/org/apache/commons/fileupload2/FileItem.java b/client/src/test/java/org/apache/commons/fileupload2/FileItem.java new file mode 100644 index 0000000000..f9e27bcbac --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/FileItem.java @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.io.UnsupportedEncodingException; + +/** + *

This class represents a file or form item that was received within a + * {@code multipart/form-data} POST request. + * + *

After retrieving an instance of this class from a {@link + * FileUpload FileUpload} instance (see + * {@link org.apache.commons.fileupload2.servlet.ServletFileUpload + * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may + * either request all contents of the file at once using {@link #get()} or + * request an {@link InputStream InputStream} with + * {@link #getInputStream()} and process the file without attempting to load + * it into memory, which may come handy with large files. + * + *

While this interface does not extend + * {@code javax.activation.DataSource} per se (to avoid a seldom used + * dependency), several of the defined methods are specifically defined with + * the same signatures as methods in that interface. This allows an + * implementation of this interface to also implement + * {@code javax.activation.DataSource} with minimal additional work. + * + * @since 1.3 additionally implements FileItemHeadersSupport + */ +public interface FileItem extends FileItemHeadersSupport { + + // ------------------------------- Methods from javax.activation.DataSource + + /** + * Returns an {@link InputStream InputStream} that can be + * used to retrieve the contents of the file. + * + * @return An {@link InputStream InputStream} that can be + * used to retrieve the contents of the file. + * @throws IOException if an error occurs. + */ + InputStream getInputStream() throws IOException; + + /** + * Returns the content type passed by the browser or {@code null} if + * not defined. + * + * @return The content type passed by the browser or {@code null} if + * not defined. + */ + String getContentType(); + + /** + * Returns the original file name in the client's file system, as provided by + * the browser (or other client software). In most cases, this will be the + * base file name, without path information. However, some clients, such as + * the Opera browser, do include path information. + * + * @return The original file name in the client's file system. + * @throws InvalidFileNameException The file name contains a NUL character, + * which might be an indicator of a security attack. If you intend to + * use the file name anyways, catch the exception and use + * InvalidFileNameException#getName(). + */ + String getName(); + + // ------------------------------------------------------- FileItem methods + + /** + * Provides a hint as to whether or not the file contents will be read + * from memory. + * + * @return {@code true} if the file contents will be read from memory; + * {@code false} otherwise. + */ + boolean isInMemory(); + + /** + * Returns the size of the file item. + * + * @return The size of the file item, in bytes. + */ + long getSize(); + + /** + * Returns the contents of the file item as an array of bytes. + * + * @return The contents of the file item as an array of bytes. + * @throws UncheckedIOException if an I/O error occurs + */ + byte[] get() throws UncheckedIOException; + + /** + * Returns the contents of the file item as a String, using the specified + * encoding. This method uses {@link #get()} to retrieve the + * contents of the item. + * + * @param encoding The character encoding to use. + * @return The contents of the item, as a string. + * @throws UnsupportedEncodingException if the requested character + * encoding is not available. + * @throws IOException if an I/O error occurs + */ + String getString(String encoding) throws UnsupportedEncodingException, IOException; + + /** + * Returns the contents of the file item as a String, using the default + * character encoding. This method uses {@link #get()} to retrieve the + * contents of the item. + * + * @return The contents of the item, as a string. + */ + String getString(); + + /** + * A convenience method to write an uploaded item to disk. The client code + * is not concerned with whether or not the item is stored in memory, or on + * disk in a temporary location. They just want to write the uploaded item + * to a file. + *

+ * This method is not guaranteed to succeed if called more than once for + * the same item. This allows a particular implementation to use, for + * example, file renaming, where possible, rather than copying all of the + * underlying data, thus gaining a significant performance benefit. + * + * @param file The {@code File} into which the uploaded item should + * be stored. + * @throws Exception if an error occurs. + */ + void write(File file) throws Exception; + + /** + * Deletes the underlying storage for a file item, including deleting any + * associated temporary disk file. Although this storage will be deleted + * automatically when the {@code FileItem} instance is garbage + * collected, this method can be used to ensure that this is done at an + * earlier time, thus preserving system resources. + */ + void delete(); + + /** + * Returns the name of the field in the multipart form corresponding to + * this file item. + * + * @return The name of the form field. + */ + String getFieldName(); + + /** + * Sets the field name used to reference this file item. + * + * @param name The name of the form field. + */ + void setFieldName(String name); + + /** + * Determines whether or not a {@code FileItem} instance represents + * a simple form field. + * + * @return {@code true} if the instance represents a simple form + * field; {@code false} if it represents an uploaded file. + */ + boolean isFormField(); + + /** + * Specifies whether or not a {@code FileItem} instance represents + * a simple form field. + * + * @param state {@code true} if the instance represents a simple form + * field; {@code false} if it represents an uploaded file. + */ + void setFormField(boolean state); + + /** + * Returns an {@link OutputStream OutputStream} that can + * be used for storing the contents of the file. + * + * @return An {@link OutputStream OutputStream} that can be used + * for storing the contents of the file. + * @throws IOException if an error occurs. + */ + OutputStream getOutputStream() throws IOException; + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/FileItemFactory.java b/client/src/test/java/org/apache/commons/fileupload2/FileItemFactory.java new file mode 100644 index 0000000000..6ba1d1ecac --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/FileItemFactory.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +/** + *

A factory interface for creating {@link FileItem} instances. Factories + * can provide their own custom configuration, over and above that provided + * by the default file upload implementation.

+ */ +public interface FileItemFactory { + + /** + * Create a new {@link FileItem} instance from the supplied parameters and + * any local factory configuration. + * + * @param fieldName The name of the form field. + * @param contentType The content type of the form field. + * @param isFormField {@code true} if this is a plain form field; + * {@code false} otherwise. + * @param fileName The name of the uploaded file, if any, as supplied + * by the browser or other client. + * @return The newly created file item. + */ + FileItem createItem( + String fieldName, + String contentType, + boolean isFormField, + String fileName + ); + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/FileItemHeaders.java b/client/src/test/java/org/apache/commons/fileupload2/FileItemHeaders.java new file mode 100644 index 0000000000..2f9e7ceae1 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/FileItemHeaders.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +import java.util.Iterator; + +/** + *

This class provides support for accessing the headers for a file or form + * item that was received within a {@code multipart/form-data} POST + * request.

+ * + * @since 1.2.1 + */ +public interface FileItemHeaders { + + /** + * Returns the value of the specified part header as a {@code String}. + *

+ * If the part did not include a header of the specified name, this method + * return {@code null}. If there are multiple headers with the same + * name, this method returns the first header in the item. The header + * name is case insensitive. + * + * @param name a {@code String} specifying the header name + * @return a {@code String} containing the value of the requested + * header, or {@code null} if the item does not have a header + * of that name + */ + String getHeader(String name); + + /** + *

+ * Returns all the values of the specified item header as an + * {@code Iterator} of {@code String} objects. + *

+ *

+ * If the item did not include any headers of the specified name, this + * method returns an empty {@code Iterator}. The header name is + * case insensitive. + *

+ * + * @param name a {@code String} specifying the header name + * @return an {@code Iterator} containing the values of the + * requested header. If the item does not have any headers of + * that name, return an empty {@code Iterator} + */ + Iterator getHeaders(String name); + + /** + *

+ * Returns an {@code Iterator} of all the header names. + *

+ * + * @return an {@code Iterator} containing all of the names of + * headers provided with this file item. If the item does not have + * any headers return an empty {@code Iterator} + */ + Iterator getHeaderNames(); + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/FileItemHeadersSupport.java b/client/src/test/java/org/apache/commons/fileupload2/FileItemHeadersSupport.java new file mode 100644 index 0000000000..61b96007f6 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/FileItemHeadersSupport.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +/** + * Interface that will indicate that {@link FileItem} or {@link FileItemStream} + * implementations will accept the headers read for the item. + * + * @see FileItem + * @see FileItemStream + * @since 1.2.1 + */ +public interface FileItemHeadersSupport { + + /** + * Returns the collection of headers defined locally within this item. + * + * @return the {@link FileItemHeaders} present for this item. + */ + FileItemHeaders getHeaders(); + + /** + * Sets the headers read from within an item. Implementations of + * {@link FileItem} or {@link FileItemStream} should implement this + * interface to be able to get the raw headers found within the item + * header block. + * + * @param headers the instance that holds onto the headers + * for this instance. + */ + void setHeaders(FileItemHeaders headers); + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/FileItemIterator.java b/client/src/test/java/org/apache/commons/fileupload2/FileItemIterator.java new file mode 100644 index 0000000000..b3e1703f5c --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/FileItemIterator.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +import org.apache.commons.fileupload2.pub.FileSizeLimitExceededException; +import org.apache.commons.fileupload2.pub.SizeLimitExceededException; + +import java.io.IOException; +import java.util.List; + +/** + * An iterator, as returned by + * {@link FileUploadBase#getItemIterator(RequestContext)}. + */ +public interface FileItemIterator { + /** + * Returns the maximum size of a single file. An {@link FileSizeLimitExceededException} + * will be thrown, if there is an uploaded file, which is exceeding this value. + * By default, this value will be copied from the {@link FileUploadBase#getFileSizeMax() + * FileUploadBase} object, however, the user may replace the default value with a + * request specific value by invoking {@link #setFileSizeMax(long)} on this object. + * + * @return The maximum size of a single, uploaded file. The value -1 indicates "unlimited". + */ + long getFileSizeMax(); + + /** + * Sets the maximum size of a single file. An {@link FileSizeLimitExceededException} + * will be thrown, if there is an uploaded file, which is exceeding this value. + * By default, this value will be copied from the {@link FileUploadBase#getFileSizeMax() + * FileUploadBase} object, however, the user may replace the default value with a + * request specific value by invoking {@link #setFileSizeMax(long)} on this object, so + * there is no need to configure it here. + * Note:Changing this value doesn't affect files, that have already been uploaded. + * + * @param pFileSizeMax The maximum size of a single, uploaded file. The value -1 indicates "unlimited". + */ + void setFileSizeMax(long pFileSizeMax); + + /** + * Returns the maximum size of the complete HTTP request. A {@link SizeLimitExceededException} + * will be thrown, if the HTTP request will exceed this value. + * By default, this value will be copied from the {@link FileUploadBase#getSizeMax() + * FileUploadBase} object, however, the user may replace the default value with a + * request specific value by invoking {@link #setSizeMax(long)} on this object. + * + * @return The maximum size of the complete HTTP request. The value -1 indicates "unlimited". + */ + long getSizeMax(); + + /** + * Returns the maximum size of the complete HTTP request. A {@link SizeLimitExceededException} + * will be thrown, if the HTTP request will exceed this value. + * By default, this value will be copied from the {@link FileUploadBase#getSizeMax() + * FileUploadBase} object, however, the user may replace the default value with a + * request specific value by invoking {@link #setSizeMax(long)} on this object. + * Note: Setting the maximum size on this object will work only, if the iterator is not + * yet initialized. In other words: If the methods {@link #hasNext()}, {@link #next()} have not + * yet been invoked. + * + * @param pSizeMax The maximum size of the complete HTTP request. The value -1 indicates "unlimited". + */ + void setSizeMax(long pSizeMax); + + /** + * Returns, whether another instance of {@link FileItemStream} + * is available. + * + * @return True, if one or more additional file items + * are available, otherwise false. + * @throws FileUploadException Parsing or processing the + * file item failed. + * @throws IOException Reading the file item failed. + */ + boolean hasNext() throws FileUploadException, IOException; + + /** + * Returns the next available {@link FileItemStream}. + * + * @return FileItemStream instance, which provides + * access to the next file item. + * @throws java.util.NoSuchElementException No more items are available. Use + * {@link #hasNext()} to prevent this exception. + * @throws FileUploadException Parsing or processing the + * file item failed. + * @throws IOException Reading the file item failed. + */ + FileItemStream next() throws FileUploadException, IOException; + + List getFileItems() throws FileUploadException, IOException; +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/FileItemStream.java b/client/src/test/java/org/apache/commons/fileupload2/FileItemStream.java new file mode 100644 index 0000000000..c72c832073 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/FileItemStream.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +import java.io.IOException; +import java.io.InputStream; + +/** + *

This interface provides access to a file or form item that was + * received within a {@code multipart/form-data} POST request. + * The items contents are retrieved by calling {@link #openStream()}.

+ *

Instances of this class are created by accessing the + * iterator, returned by + * {@link FileUploadBase#getItemIterator(RequestContext)}.

+ *

Note: There is an interaction between the iterator and + * its associated instances of {@link FileItemStream}: By invoking + * {@link java.util.Iterator#hasNext()} on the iterator, you discard all data, + * which hasn't been read so far from the previous data.

+ */ +public interface FileItemStream extends FileItemHeadersSupport { + + /** + * This exception is thrown, if an attempt is made to read + * data from the {@link InputStream}, which has been returned + * by {@link FileItemStream#openStream()}, after + * {@link java.util.Iterator#hasNext()} has been invoked on the + * iterator, which created the {@link FileItemStream}. + */ + class ItemSkippedException extends IOException { + + /** + * The exceptions serial version UID, which is being used + * when serializing an exception instance. + */ + private static final long serialVersionUID = -7280778431581963740L; + + } + + /** + * Creates an {@link InputStream}, which allows to read the + * items contents. + * + * @return The input stream, from which the items data may + * be read. + * @throws IllegalStateException The method was already invoked on + * this item. It is not possible to recreate the data stream. + * @throws IOException An I/O error occurred. + * @see ItemSkippedException + */ + InputStream openStream() throws IOException; + + /** + * Returns the content type passed by the browser or {@code null} if + * not defined. + * + * @return The content type passed by the browser or {@code null} if + * not defined. + */ + String getContentType(); + + /** + * Returns the original file name in the client's file system, as provided by + * the browser (or other client software). In most cases, this will be the + * base file name, without path information. However, some clients, such as + * the Opera browser, do include path information. + * + * @return The original file name in the client's file system. + */ + String getName(); + + /** + * Returns the name of the field in the multipart form corresponding to + * this file item. + * + * @return The name of the form field. + */ + String getFieldName(); + + /** + * Determines whether or not a {@code FileItem} instance represents + * a simple form field. + * + * @return {@code true} if the instance represents a simple form + * field; {@code false} if it represents an uploaded file. + */ + boolean isFormField(); + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/FileUpload.java b/client/src/test/java/org/apache/commons/fileupload2/FileUpload.java new file mode 100644 index 0000000000..45a0312e13 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/FileUpload.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * {@code multipart/mixed} encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(RequestContext)} to acquire a list + * of {@link FileItem FileItems} associated + * with a given HTML widget.

+ * + *

How the data for individual parts is stored is determined by the factory + * used to create them; a given part may be in memory, on disk, or somewhere + * else.

+ */ +public class FileUpload + extends FileUploadBase { + + // ----------------------------------------------------------- Data members + + /** + * The factory to use to create new form items. + */ + private FileItemFactory fileItemFactory; + + // ----------------------------------------------------------- Constructors + + /** + * Constructs an uninitialized instance of this class. + *

+ * A factory must be + * configured, using {@code setFileItemFactory()}, before attempting + * to parse requests. + * + * @see #FileUpload(FileItemFactory) + */ + public FileUpload() { + } + + /** + * Constructs an instance of this class which uses the supplied factory to + * create {@code FileItem} instances. + * + * @param fileItemFactory The factory to use for creating file items. + * @see #FileUpload() + */ + public FileUpload(final FileItemFactory fileItemFactory) { + this.fileItemFactory = fileItemFactory; + } + + // ----------------------------------------------------- Property accessors + + /** + * Returns the factory class used when creating file items. + * + * @return The factory class for new file items. + */ + @Override + public FileItemFactory getFileItemFactory() { + return fileItemFactory; + } + + /** + * Sets the factory class to use when creating file items. + * + * @param factory The factory class for new file items. + */ + @Override + public void setFileItemFactory(final FileItemFactory factory) { + this.fileItemFactory = factory; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/FileUploadBase.java b/client/src/test/java/org/apache/commons/fileupload2/FileUploadBase.java new file mode 100644 index 0000000000..d375518eb7 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/FileUploadBase.java @@ -0,0 +1,651 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +import org.apache.commons.fileupload2.impl.FileItemIteratorImpl; +import org.apache.commons.fileupload2.pub.FileUploadIOException; +import org.apache.commons.fileupload2.pub.IOFileUploadException; +import org.apache.commons.fileupload2.util.FileItemHeadersImpl; +import org.apache.commons.fileupload2.util.Streams; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +import static java.lang.String.format; + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * {@code multipart/mixed} encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(RequestContext)} to acquire a list of {@link + * FileItem}s associated with a given HTML + * widget.

+ * + *

How the data for individual parts is stored is determined by the factory + * used to create them; a given part may be in memory, on disk, or somewhere + * else.

+ */ +public abstract class FileUploadBase { + + // ---------------------------------------------------------- Class methods + + /** + *

Utility method that determines whether the request contains multipart + * content.

+ * + *

NOTE:This method will be moved to the + * {@code ServletFileUpload} class after the FileUpload 1.1 release. + * Unfortunately, since this method is static, it is not possible to + * provide its replacement until this method is removed.

+ * + * @param ctx The request context to be evaluated. Must be non-null. + * @return {@code true} if the request is multipart; + * {@code false} otherwise. + */ + public static final boolean isMultipartContent(final RequestContext ctx) { + final String contentType = ctx.getContentType(); + if (contentType == null) { + return false; + } + return contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART); + } + + // ----------------------------------------------------- Manifest constants + + /** + * HTTP content type header name. + */ + public static final String CONTENT_TYPE = "Content-type"; + + /** + * HTTP content disposition header name. + */ + public static final String CONTENT_DISPOSITION = "Content-disposition"; + + /** + * HTTP content length header name. + */ + public static final String CONTENT_LENGTH = "Content-length"; + + /** + * Content-disposition value for form data. + */ + public static final String FORM_DATA = "form-data"; + + /** + * Content-disposition value for file attachment. + */ + public static final String ATTACHMENT = "attachment"; + + /** + * Part of HTTP content type header. + */ + public static final String MULTIPART = "multipart/"; + + /** + * HTTP content type header for multipart forms. + */ + public static final String MULTIPART_FORM_DATA = "multipart/form-data"; + + /** + * HTTP content type header for multiple uploads. + */ + public static final String MULTIPART_MIXED = "multipart/mixed"; + + /** + * The maximum length of a single header line that will be parsed + * (1024 bytes). + * + * @deprecated This constant is no longer used. As of commons-fileupload + * 1.2, the only applicable limit is the total size of a parts headers, + * {@link MultipartStream#HEADER_PART_SIZE_MAX}. + */ + @Deprecated + public static final int MAX_HEADER_SIZE = 1024; + + // ----------------------------------------------------------- Data members + + /** + * The maximum size permitted for the complete request, as opposed to + * {@link #fileSizeMax}. A value of -1 indicates no maximum. + */ + private long sizeMax = -1; + + /** + * The maximum size permitted for a single uploaded file, as opposed + * to {@link #sizeMax}. A value of -1 indicates no maximum. + */ + private long fileSizeMax = -1; + + /** + * The content encoding to use when reading part headers. + */ + private String headerEncoding; + + /** + * The progress listener. + */ + private ProgressListener listener; + + // ----------------------------------------------------- Property accessors + + /** + * Returns the factory class used when creating file items. + * + * @return The factory class for new file items. + */ + public abstract FileItemFactory getFileItemFactory(); + + /** + * Sets the factory class to use when creating file items. + * + * @param factory The factory class for new file items. + */ + public abstract void setFileItemFactory(FileItemFactory factory); + + /** + * Returns the maximum allowed size of a complete request, as opposed + * to {@link #getFileSizeMax()}. + * + * @return The maximum allowed size, in bytes. The default value of + * -1 indicates, that there is no limit. + * @see #setSizeMax(long) + */ + public long getSizeMax() { + return sizeMax; + } + + /** + * Sets the maximum allowed size of a complete request, as opposed + * to {@link #setFileSizeMax(long)}. + * + * @param sizeMax The maximum allowed size, in bytes. The default value of + * -1 indicates, that there is no limit. + * @see #getSizeMax() + */ + public void setSizeMax(final long sizeMax) { + this.sizeMax = sizeMax; + } + + /** + * Returns the maximum allowed size of a single uploaded file, + * as opposed to {@link #getSizeMax()}. + * + * @return Maximum size of a single uploaded file. + * @see #setFileSizeMax(long) + */ + public long getFileSizeMax() { + return fileSizeMax; + } + + /** + * Sets the maximum allowed size of a single uploaded file, + * as opposed to {@link #getSizeMax()}. + * + * @param fileSizeMax Maximum size of a single uploaded file. + * @see #getFileSizeMax() + */ + public void setFileSizeMax(final long fileSizeMax) { + this.fileSizeMax = fileSizeMax; + } + + /** + * Retrieves the character encoding used when reading the headers of an + * individual part. When not specified, or {@code null}, the request + * encoding is used. If that is also not specified, or {@code null}, + * the platform default encoding is used. + * + * @return The encoding used to read part headers. + */ + public String getHeaderEncoding() { + return headerEncoding; + } + + /** + * Specifies the character encoding to be used when reading the headers of + * individual part. When not specified, or {@code null}, the request + * encoding is used. If that is also not specified, or {@code null}, + * the platform default encoding is used. + * + * @param encoding The encoding used to read part headers. + */ + public void setHeaderEncoding(final String encoding) { + headerEncoding = encoding; + } + + // --------------------------------------------------------- Public methods + + /** + * Processes an RFC 1867 + * compliant {@code multipart/form-data} stream. + * + * @param ctx The context for the request to be parsed. + * @return An iterator to instances of {@code FileItemStream} + * parsed from the request, in the order that they were + * transmitted. + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + * @throws IOException An I/O error occurred. This may be a network + * error while communicating with the client or a problem while + * storing the uploaded content. + */ + public FileItemIterator getItemIterator(final RequestContext ctx) + throws FileUploadException, IOException { + try { + return new FileItemIteratorImpl(this, ctx); + } catch (final FileUploadIOException e) { + // unwrap encapsulated SizeException + throw (FileUploadException) e.getCause(); + } + } + + /** + * Processes an RFC 1867 + * compliant {@code multipart/form-data} stream. + * + * @param ctx The context for the request to be parsed. + * @return A list of {@code FileItem} instances parsed from the + * request, in the order that they were transmitted. + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + */ + public List parseRequest(final RequestContext ctx) + throws FileUploadException { + final List items = new ArrayList<>(); + boolean successful = false; + try { + final FileItemIterator iter = getItemIterator(ctx); + final FileItemFactory fileItemFactory = Objects.requireNonNull(getFileItemFactory(), + "No FileItemFactory has been set."); + final byte[] buffer = new byte[Streams.DEFAULT_BUFFER_SIZE]; + while (iter.hasNext()) { + final FileItemStream item = iter.next(); + // Don't use getName() here to prevent an InvalidFileNameException. + final String fileName = item.getName(); + final FileItem fileItem = fileItemFactory.createItem(item.getFieldName(), item.getContentType(), + item.isFormField(), fileName); + items.add(fileItem); + try { + Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer); + } catch (final FileUploadIOException e) { + throw (FileUploadException) e.getCause(); + } catch (final IOException e) { + throw new IOFileUploadException(format("Processing of %s request failed. %s", + MULTIPART_FORM_DATA, e.getMessage()), e); + } + final FileItemHeaders fih = item.getHeaders(); + fileItem.setHeaders(fih); + } + successful = true; + return items; + } catch (final FileUploadException e) { + throw e; + } catch (final IOException e) { + throw new FileUploadException(e.getMessage(), e); + } finally { + if (!successful) { + for (final FileItem fileItem : items) { + try { + fileItem.delete(); + } catch (final Exception ignored) { + // ignored TODO perhaps add to tracker delete failure list somehow? + } + } + } + } + } + + /** + * Processes an RFC 1867 + * compliant {@code multipart/form-data} stream. + * + * @param ctx The context for the request to be parsed. + * @return A map of {@code FileItem} instances parsed from the request. + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + * @since 1.3 + */ + public Map> parseParameterMap(final RequestContext ctx) + throws FileUploadException { + final List items = parseRequest(ctx); + final Map> itemsMap = new HashMap<>(items.size()); + + for (final FileItem fileItem : items) { + final String fieldName = fileItem.getFieldName(); + final List mappedItems = itemsMap.computeIfAbsent(fieldName, k -> new ArrayList<>()); + + mappedItems.add(fileItem); + } + + return itemsMap; + } + + // ------------------------------------------------------ Protected methods + + /** + * Retrieves the boundary from the {@code Content-type} header. + * + * @param contentType The value of the content type header from which to + * extract the boundary value. + * @return The boundary, as a byte array. + */ + public byte[] getBoundary(final String contentType) { + final ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + // Parameter parser can handle null input + final Map params = parser.parse(contentType, new char[]{';', ','}); + final String boundaryStr = params.get("boundary"); + + if (boundaryStr == null) { + return null; + } + final byte[] boundary; + boundary = boundaryStr.getBytes(StandardCharsets.ISO_8859_1); + return boundary; + } + + /** + * Retrieves the file name from the {@code Content-disposition} + * header. + * + * @param headers A {@code Map} containing the HTTP request headers. + * @return The file name for the current {@code encapsulation}. + * @deprecated 1.2.1 Use {@link #getFileName(FileItemHeaders)}. + */ + @Deprecated + protected String getFileName(final Map headers) { + return getFileName(getHeader(headers, CONTENT_DISPOSITION)); + } + + /** + * Retrieves the file name from the {@code Content-disposition} + * header. + * + * @param headers The HTTP headers object. + * @return The file name for the current {@code encapsulation}. + */ + public String getFileName(final FileItemHeaders headers) { + return getFileName(headers.getHeader(CONTENT_DISPOSITION)); + } + + /** + * Returns the given content-disposition headers file name. + * + * @param pContentDisposition The content-disposition headers value. + * @return The file name + */ + private String getFileName(final String pContentDisposition) { + String fileName = null; + if (pContentDisposition != null) { + final String cdl = pContentDisposition.toLowerCase(Locale.ENGLISH); + if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) { + final ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + // Parameter parser can handle null input + final Map params = parser.parse(pContentDisposition, ';'); + if (params.containsKey("filename")) { + fileName = params.get("filename"); + if (fileName != null) { + fileName = fileName.trim(); + } else { + // Even if there is no value, the parameter is present, + // so we return an empty file name rather than no file + // name. + fileName = ""; + } + } + } + } + return fileName; + } + + /** + * Retrieves the field name from the {@code Content-disposition} + * header. + * + * @param headers A {@code Map} containing the HTTP request headers. + * @return The field name for the current {@code encapsulation}. + */ + public String getFieldName(final FileItemHeaders headers) { + return getFieldName(headers.getHeader(CONTENT_DISPOSITION)); + } + + /** + * Returns the field name, which is given by the content-disposition + * header. + * + * @param pContentDisposition The content-dispositions header value. + * @return The field jake + */ + private String getFieldName(final String pContentDisposition) { + String fieldName = null; + if (pContentDisposition != null + && pContentDisposition.toLowerCase(Locale.ENGLISH).startsWith(FORM_DATA)) { + final ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + // Parameter parser can handle null input + final Map params = parser.parse(pContentDisposition, ';'); + fieldName = params.get("name"); + if (fieldName != null) { + fieldName = fieldName.trim(); + } + } + return fieldName; + } + + /** + * Retrieves the field name from the {@code Content-disposition} + * header. + * + * @param headers A {@code Map} containing the HTTP request headers. + * @return The field name for the current {@code encapsulation}. + * @deprecated 1.2.1 Use {@link #getFieldName(FileItemHeaders)}. + */ + @Deprecated + protected String getFieldName(final Map headers) { + return getFieldName(getHeader(headers, CONTENT_DISPOSITION)); + } + + /** + * Creates a new {@link FileItem} instance. + * + * @param headers A {@code Map} containing the HTTP request + * headers. + * @param isFormField Whether or not this item is a form field, as + * opposed to a file. + * @return A newly created {@code FileItem} instance. + * @throws FileUploadException if an error occurs. + * @deprecated 1.2 This method is no longer used in favour of + * internally created instances of {@link FileItem}. + */ + @Deprecated + protected FileItem createItem(final Map headers, + final boolean isFormField) + throws FileUploadException { + return getFileItemFactory().createItem(getFieldName(headers), + getHeader(headers, CONTENT_TYPE), + isFormField, + getFileName(headers)); + } + + /** + *

Parses the {@code header-part} and returns as key/value + * pairs. + * + *

If there are multiple headers of the same names, the name + * will map to a comma-separated list containing the values. + * + * @param headerPart The {@code header-part} of the current + * {@code encapsulation}. + * @return A {@code Map} containing the parsed HTTP request headers. + */ + public FileItemHeaders getParsedHeaders(final String headerPart) { + final int len = headerPart.length(); + final FileItemHeadersImpl headers = newFileItemHeaders(); + int start = 0; + for (; ; ) { + int end = parseEndOfLine(headerPart, start); + if (start == end) { + break; + } + final StringBuilder header = new StringBuilder(headerPart.substring(start, end)); + start = end + 2; + while (start < len) { + int nonWs = start; + while (nonWs < len) { + final char c = headerPart.charAt(nonWs); + if (c != ' ' && c != '\t') { + break; + } + ++nonWs; + } + if (nonWs == start) { + break; + } + // Continuation line found + end = parseEndOfLine(headerPart, nonWs); + header.append(' ').append(headerPart, nonWs, end); + start = end + 2; + } + parseHeaderLine(headers, header.toString()); + } + return headers; + } + + /** + * Creates a new instance of {@link FileItemHeaders}. + * + * @return The new instance. + */ + protected FileItemHeadersImpl newFileItemHeaders() { + return new FileItemHeadersImpl(); + } + + /** + *

Parses the {@code header-part} and returns as key/value + * pairs. + * + *

If there are multiple headers of the same names, the name + * will map to a comma-separated list containing the values. + * + * @param headerPart The {@code header-part} of the current + * {@code encapsulation}. + * @return A {@code Map} containing the parsed HTTP request headers. + * @deprecated 1.2.1 Use {@link #getParsedHeaders(String)} + */ + @Deprecated + protected Map parseHeaders(final String headerPart) { + final FileItemHeaders headers = getParsedHeaders(headerPart); + final Map result = new HashMap<>(); + for (final Iterator iter = headers.getHeaderNames(); iter.hasNext(); ) { + final String headerName = iter.next(); + final Iterator iter2 = headers.getHeaders(headerName); + final StringBuilder headerValue = new StringBuilder(iter2.next()); + while (iter2.hasNext()) { + headerValue.append(",").append(iter2.next()); + } + result.put(headerName, headerValue.toString()); + } + return result; + } + + /** + * Skips bytes until the end of the current line. + * + * @param headerPart The headers, which are being parsed. + * @param end Index of the last byte, which has yet been + * processed. + * @return Index of the \r\n sequence, which indicates + * end of line. + */ + private int parseEndOfLine(final String headerPart, final int end) { + int index = end; + for (; ; ) { + final int offset = headerPart.indexOf('\r', index); + if (offset == -1 || offset + 1 >= headerPart.length()) { + throw new IllegalStateException( + "Expected headers to be terminated by an empty line."); + } + if (headerPart.charAt(offset + 1) == '\n') { + return offset; + } + index = offset + 1; + } + } + + /** + * Reads the next header line. + * + * @param headers String with all headers. + * @param header Map where to store the current header. + */ + private void parseHeaderLine(final FileItemHeadersImpl headers, final String header) { + final int colonOffset = header.indexOf(':'); + if (colonOffset == -1) { + // This header line is malformed, skip it. + return; + } + final String headerName = header.substring(0, colonOffset).trim(); + final String headerValue = + header.substring(colonOffset + 1).trim(); + headers.addHeader(headerName, headerValue); + } + + /** + * Returns the header with the specified name from the supplied map. The + * header lookup is case-insensitive. + * + * @param headers A {@code Map} containing the HTTP request headers. + * @param name The name of the header to return. + * @return The value of specified header, or a comma-separated list if + * there were multiple headers of that name. + * @deprecated 1.2.1 Use {@link FileItemHeaders#getHeader(String)}. + */ + @Deprecated + protected final String getHeader(final Map headers, + final String name) { + return headers.get(name.toLowerCase(Locale.ENGLISH)); + } + + /** + * Returns the progress listener. + * + * @return The progress listener, if any, or null. + */ + public ProgressListener getProgressListener() { + return listener; + } + + /** + * Sets the progress listener. + * + * @param pListener The progress listener, if any. Defaults to null. + */ + public void setProgressListener(final ProgressListener pListener) { + listener = pListener; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/FileUploadException.java b/client/src/test/java/org/apache/commons/fileupload2/FileUploadException.java new file mode 100644 index 0000000000..ba46272af8 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/FileUploadException.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * Exception for errors encountered while processing the request. + */ +public class FileUploadException extends IOException { + + /** + * Serial version UID, being used, if the exception + * is serialized. + */ + private static final long serialVersionUID = 8881893724388807504L; + + /** + * The exceptions cause. We overwrite the cause of + * the super class, which isn't available in Java 1.3. + */ + private final Throwable cause; + + /** + * Constructs a new {@code FileUploadException} without message. + */ + public FileUploadException() { + this(null, null); + } + + /** + * Constructs a new {@code FileUploadException} with specified detail + * message. + * + * @param msg the error message. + */ + public FileUploadException(final String msg) { + this(msg, null); + } + + /** + * Creates a new {@code FileUploadException} with the given + * detail message and cause. + * + * @param msg The exceptions detail message. + * @param cause The exceptions cause. + */ + public FileUploadException(final String msg, final Throwable cause) { + super(msg); + this.cause = cause; + } + + /** + * Prints this throwable and its backtrace to the specified print stream. + * + * @param stream {@code PrintStream} to use for output + */ + @Override + public void printStackTrace(final PrintStream stream) { + super.printStackTrace(stream); + if (cause != null) { + stream.println("Caused by:"); + cause.printStackTrace(stream); + } + } + + /** + * Prints this throwable and its backtrace to the specified + * print writer. + * + * @param writer {@code PrintWriter} to use for output + */ + @Override + public void printStackTrace(final PrintWriter writer) { + super.printStackTrace(writer); + if (cause != null) { + writer.println("Caused by:"); + cause.printStackTrace(writer); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Throwable getCause() { + return cause; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/InvalidFileNameException.java b/client/src/test/java/org/apache/commons/fileupload2/InvalidFileNameException.java new file mode 100644 index 0000000000..3b940be1ef --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/InvalidFileNameException.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +/** + * This exception is thrown in case of an invalid file name. + * A file name is invalid, if it contains a NUL character. + * Attackers might use this to circumvent security checks: + * For example, a malicious user might upload a file with the name + * "foo.exe\0.png". This file name might pass security checks (i.e. + * checks for the extension ".png"), while, depending on the underlying + * C library, it might create a file named "foo.exe", as the NUL + * character is the string terminator in C. + */ +public class InvalidFileNameException extends RuntimeException { + + /** + * Serial version UID, being used, if the exception + * is serialized. + */ + private static final long serialVersionUID = 7922042602454350470L; + + /** + * The file name causing the exception. + */ + private final String name; + + /** + * Creates a new instance. + * + * @param pName The file name causing the exception. + * @param pMessage A human readable error message. + */ + public InvalidFileNameException(final String pName, final String pMessage) { + super(pMessage); + name = pName; + } + + /** + * Returns the invalid file name. + * + * @return the invalid file name. + */ + public String getName() { + return name; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/MultipartStream.java b/client/src/test/java/org/apache/commons/fileupload2/MultipartStream.java new file mode 100644 index 0000000000..4198a37ba0 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/MultipartStream.java @@ -0,0 +1,1044 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +import org.apache.commons.fileupload2.pub.FileUploadIOException; +import org.apache.commons.fileupload2.util.Closeable; +import org.apache.commons.fileupload2.util.Streams; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import static java.lang.String.format; + +/** + *

Low level API for processing file uploads. + * + *

This class can be used to process data streams conforming to MIME + * 'multipart' format as defined in + * RFC 1867. Arbitrarily + * large amounts of data in the stream can be processed under constant + * memory usage. + * + *

The format of the stream is defined in the following way:
+ * + * + * multipart-body := preamble 1*encapsulation close-delimiter epilogue
+ * encapsulation := delimiter body CRLF
+ * delimiter := "--" boundary CRLF
+ * close-delimiter := "--" boundary "--"
+ * preamble := <ignore>
+ * epilogue := <ignore>
+ * body := header-part CRLF body-part
+ * header-part := 1*header CRLF
+ * header := header-name ":" header-value
+ * header-name := <printable ascii characters except ":">
+ * header-value := <any ascii characters except CR & LF>
+ * body-data := <arbitrary data>
+ *
+ * + *

Note that body-data can contain another mulipart entity. There + * is limited support for single pass processing of such nested + * streams. The nested stream is required to have a + * boundary token of the same length as the parent stream (see {@link + * #setBoundary(byte[])}). + * + *

Here is an example of usage of this class.
+ * + *

+ *   try {
+ *     MultipartStream multipartStream = new MultipartStream(input, boundary);
+ *     boolean nextPart = multipartStream.skipPreamble();
+ *     OutputStream output;
+ *     while(nextPart) {
+ *       String header = multipartStream.readHeaders();
+ *       // process headers
+ *       // create some output stream
+ *       multipartStream.readBodyData(output);
+ *       nextPart = multipartStream.readBoundary();
+ *     }
+ *   } catch(MultipartStream.MalformedStreamException e) {
+ *     // the stream failed to follow required syntax
+ *   } catch(IOException e) {
+ *     // a read or write error occurred
+ *   }
+ * 
+ */ +public class MultipartStream { + + /** + * Internal class, which is used to invoke the + * {@link ProgressListener}. + */ + public static class ProgressNotifier { + + /** + * The listener to invoke. + */ + private final ProgressListener listener; + + /** + * Number of expected bytes, if known, or -1. + */ + private final long contentLength; + + /** + * Number of bytes, which have been read so far. + */ + private long bytesRead; + + /** + * Number of items, which have been read so far. + */ + private int items; + + /** + * Creates a new instance with the given listener + * and content length. + * + * @param pListener The listener to invoke. + * @param pContentLength The expected content length. + */ + public ProgressNotifier(final ProgressListener pListener, final long pContentLength) { + listener = pListener; + contentLength = pContentLength; + } + + /** + * Called to indicate that bytes have been read. + * + * @param pBytes Number of bytes, which have been read. + */ + void noteBytesRead(final int pBytes) { + /* Indicates, that the given number of bytes have been read from + * the input stream. + */ + bytesRead += pBytes; + notifyListener(); + } + + /** + * Called to indicate, that a new file item has been detected. + */ + public void noteItem() { + ++items; + notifyListener(); + } + + /** + * Called for notifying the listener. + */ + private void notifyListener() { + if (listener != null) { + listener.update(bytesRead, contentLength, items); + } + } + + } + + // ----------------------------------------------------- Manifest constants + + /** + * The Carriage Return ASCII character value. + */ + public static final byte CR = 0x0D; + + /** + * The Line Feed ASCII character value. + */ + public static final byte LF = 0x0A; + + /** + * The dash (-) ASCII character value. + */ + public static final byte DASH = 0x2D; + + /** + * The maximum length of {@code header-part} that will be + * processed (10 kilobytes = 10240 bytes.). + */ + public static final int HEADER_PART_SIZE_MAX = 10240; + + /** + * The default length of the buffer used for processing a request. + */ + protected static final int DEFAULT_BUFSIZE = 4096; + + /** + * A byte sequence that marks the end of {@code header-part} + * ({@code CRLFCRLF}). + */ + protected static final byte[] HEADER_SEPARATOR = {CR, LF, CR, LF}; + + /** + * A byte sequence that that follows a delimiter that will be + * followed by an encapsulation ({@code CRLF}). + */ + protected static final byte[] FIELD_SEPARATOR = {CR, LF}; + + /** + * A byte sequence that that follows a delimiter of the last + * encapsulation in the stream ({@code --}). + */ + protected static final byte[] STREAM_TERMINATOR = {DASH, DASH}; + + /** + * A byte sequence that precedes a boundary ({@code CRLF--}). + */ + protected static final byte[] BOUNDARY_PREFIX = {CR, LF, DASH, DASH}; + + // ----------------------------------------------------------- Data members + + /** + * The input stream from which data is read. + */ + private final InputStream input; + + /** + * The length of the boundary token plus the leading {@code CRLF--}. + */ + private int boundaryLength; + + /** + * The amount of data, in bytes, that must be kept in the buffer in order + * to detect delimiters reliably. + */ + private final int keepRegion; + + /** + * The byte sequence that partitions the stream. + */ + private final byte[] boundary; + + /** + * The table for Knuth-Morris-Pratt search algorithm. + */ + private final int[] boundaryTable; + + /** + * The length of the buffer used for processing the request. + */ + private final int bufSize; + + /** + * The buffer used for processing the request. + */ + private final byte[] buffer; + + /** + * The index of first valid character in the buffer. + *
+ * 0 <= head < bufSize + */ + private int head; + + /** + * The index of last valid character in the buffer + 1. + *
+ * 0 <= tail <= bufSize + */ + private int tail; + + /** + * The content encoding to use when reading headers. + */ + private String headerEncoding; + + /** + * The progress notifier, if any, or null. + */ + private final ProgressNotifier notifier; + + // ----------------------------------------------------------- Constructors + + /** + * Creates a new instance. + * + * @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int, + * ProgressNotifier)} + */ + @Deprecated + public MultipartStream() { + this(null, null, null); + } + + /** + *

Constructs a {@code MultipartStream} with a custom size buffer + * and no progress notifier. + * + *

Note that the buffer must be at least big enough to contain the + * boundary string, plus 4 characters for CR/LF and double dash, plus at + * least one byte of data. Too small a buffer size setting will degrade + * performance. + * + * @param input The {@code InputStream} to serve as a data source. + * @param boundary The token used for dividing the stream into + * {@code encapsulations}. + * @param bufSize The size of the buffer to be used, in bytes. + * @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int, + * ProgressNotifier)}. + */ + @Deprecated + public MultipartStream(final InputStream input, final byte[] boundary, final int bufSize) { + this(input, boundary, bufSize, null); + } + + /** + *

Constructs a {@code MultipartStream} with a custom size buffer. + * + *

Note that the buffer must be at least big enough to contain the + * boundary string, plus 4 characters for CR/LF and double dash, plus at + * least one byte of data. Too small a buffer size setting will degrade + * performance. + * + * @param input The {@code InputStream} to serve as a data source. + * @param boundary The token used for dividing the stream into + * {@code encapsulations}. + * @param bufSize The size of the buffer to be used, in bytes. + * @param pNotifier The notifier, which is used for calling the + * progress listener, if any. + * @throws IllegalArgumentException If the buffer size is too small + * @since 1.3.1 + */ + public MultipartStream(final InputStream input, + final byte[] boundary, + final int bufSize, + final ProgressNotifier pNotifier) { + + if (boundary == null) { + throw new IllegalArgumentException("boundary may not be null"); + } + // We prepend CR/LF to the boundary to chop trailing CR/LF from + // body-data tokens. + this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length; + if (bufSize < this.boundaryLength + 1) { + throw new IllegalArgumentException( + "The buffer size specified for the MultipartStream is too small"); + } + + this.input = input; + this.bufSize = Math.max(bufSize, boundaryLength * 2); + this.buffer = new byte[this.bufSize]; + this.notifier = pNotifier; + + this.boundary = new byte[this.boundaryLength]; + this.boundaryTable = new int[this.boundaryLength + 1]; + this.keepRegion = this.boundary.length; + + System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0, + BOUNDARY_PREFIX.length); + System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, + boundary.length); + computeBoundaryTable(); + + head = 0; + tail = 0; + } + + /** + *

Constructs a {@code MultipartStream} with a default size buffer. + * + * @param input The {@code InputStream} to serve as a data source. + * @param boundary The token used for dividing the stream into + * {@code encapsulations}. + * @param pNotifier An object for calling the progress listener, if any. + * @see #MultipartStream(InputStream, byte[], int, ProgressNotifier) + */ + public MultipartStream(final InputStream input, + final byte[] boundary, + final ProgressNotifier pNotifier) { + this(input, boundary, DEFAULT_BUFSIZE, pNotifier); + } + + /** + *

Constructs a {@code MultipartStream} with a default size buffer. + * + * @param input The {@code InputStream} to serve as a data source. + * @param boundary The token used for dividing the stream into + * {@code encapsulations}. + * @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int, + * ProgressNotifier)}. + */ + @Deprecated + public MultipartStream(final InputStream input, + final byte[] boundary) { + this(input, boundary, DEFAULT_BUFSIZE, null); + } + + // --------------------------------------------------------- Public methods + + /** + * Retrieves the character encoding used when reading the headers of an + * individual part. When not specified, or {@code null}, the platform + * default encoding is used. + * + * @return The encoding used to read part headers. + */ + public String getHeaderEncoding() { + return headerEncoding; + } + + /** + * Specifies the character encoding to be used when reading the headers of + * individual parts. When not specified, or {@code null}, the platform + * default encoding is used. + * + * @param encoding The encoding used to read part headers. + */ + public void setHeaderEncoding(final String encoding) { + headerEncoding = encoding; + } + + /** + * Reads a byte from the {@code buffer}, and refills it as + * necessary. + * + * @return The next byte from the input stream. + * @throws IOException if there is no more data available. + */ + public byte readByte() throws IOException { + // Buffer depleted ? + if (head == tail) { + head = 0; + // Refill. + tail = input.read(buffer, head, bufSize); + if (tail == -1) { + // No more data available. + throw new IOException("No more data is available"); + } + if (notifier != null) { + notifier.noteBytesRead(tail); + } + } + return buffer[head++]; + } + + /** + * Skips a {@code boundary} token, and checks whether more + * {@code encapsulations} are contained in the stream. + * + * @return {@code true} if there are more encapsulations in + * this stream; {@code false} otherwise. + * @throws FileUploadIOException if the bytes read from the stream exceeded the size limits + * @throws MalformedStreamException if the stream ends unexpectedly or + * fails to follow required syntax. + */ + public boolean readBoundary() + throws FileUploadIOException, MalformedStreamException { + final byte[] marker = new byte[2]; + final boolean nextChunk; + + head += boundaryLength; + try { + marker[0] = readByte(); + if (marker[0] == LF) { + // Work around IE5 Mac bug with input type=image. + // Because the boundary delimiter, not including the trailing + // CRLF, must not appear within any file (RFC 2046, section + // 5.1.1), we know the missing CR is due to a buggy browser + // rather than a file containing something similar to a + // boundary. + return true; + } + + marker[1] = readByte(); + if (arrayequals(marker, STREAM_TERMINATOR, 2)) { + nextChunk = false; + } else if (arrayequals(marker, FIELD_SEPARATOR, 2)) { + nextChunk = true; + } else { + throw new MalformedStreamException( + "Unexpected characters follow a boundary"); + } + } catch (final FileUploadIOException e) { + // wraps a SizeException, re-throw as it will be unwrapped later + throw e; + } catch (final IOException e) { + throw new MalformedStreamException("Stream ended unexpectedly"); + } + return nextChunk; + } + + /** + *

Changes the boundary token used for partitioning the stream. + * + *

This method allows single pass processing of nested multipart + * streams. + * + *

The boundary token of the nested stream is {@code required} + * to be of the same length as the boundary token in parent stream. + * + *

Restoring the parent stream boundary token after processing of a + * nested stream is left to the application. + * + * @param boundary The boundary to be used for parsing of the nested + * stream. + * @throws IllegalBoundaryException if the {@code boundary} + * has a different length than the one + * being currently parsed. + */ + public void setBoundary(final byte[] boundary) + throws IllegalBoundaryException { + if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) { + throw new IllegalBoundaryException( + "The length of a boundary token cannot be changed"); + } + System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, + boundary.length); + computeBoundaryTable(); + } + + /** + * Compute the table used for Knuth-Morris-Pratt search algorithm. + */ + private void computeBoundaryTable() { + int position = 2; + int candidate = 0; + + boundaryTable[0] = -1; + boundaryTable[1] = 0; + + while (position <= boundaryLength) { + if (boundary[position - 1] == boundary[candidate]) { + boundaryTable[position] = candidate + 1; + candidate++; + position++; + } else if (candidate > 0) { + candidate = boundaryTable[candidate]; + } else { + boundaryTable[position] = 0; + position++; + } + } + } + + /** + *

Reads the {@code header-part} of the current + * {@code encapsulation}. + * + *

Headers are returned verbatim to the input stream, including the + * trailing {@code CRLF} marker. Parsing is left to the + * application. + * + *

TODO allow limiting maximum header size to + * protect against abuse. + * + * @return The {@code header-part} of the current encapsulation. + * @throws FileUploadIOException if the bytes read from the stream exceeded the size limits. + * @throws MalformedStreamException if the stream ends unexpectedly. + */ + public String readHeaders() throws FileUploadIOException, MalformedStreamException { + int i = 0; + byte b; + // to support multi-byte characters + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int size = 0; + while (i < HEADER_SEPARATOR.length) { + try { + b = readByte(); + } catch (final FileUploadIOException e) { + // wraps a SizeException, re-throw as it will be unwrapped later + throw e; + } catch (final IOException e) { + throw new MalformedStreamException("Stream ended unexpectedly"); + } + if (++size > HEADER_PART_SIZE_MAX) { + throw new MalformedStreamException( + format("Header section has more than %s bytes (maybe it is not properly terminated)", + HEADER_PART_SIZE_MAX)); + } + if (b == HEADER_SEPARATOR[i]) { + i++; + } else { + i = 0; + } + baos.write(b); + } + + String headers; + if (headerEncoding != null) { + try { + headers = baos.toString(headerEncoding); + } catch (final UnsupportedEncodingException e) { + // Fall back to platform default if specified encoding is not + // supported. + headers = baos.toString(); + } + } else { + headers = baos.toString(); + } + + return headers; + } + + /** + *

Reads {@code body-data} from the current + * {@code encapsulation} and writes its contents into the + * output {@code Stream}. + * + *

Arbitrary large amounts of data can be processed by this + * method using a constant size buffer. (see {@link + * #MultipartStream(InputStream, byte[], int, + * ProgressNotifier) constructor}). + * + * @param output The {@code Stream} to write data into. May + * be null, in which case this method is equivalent + * to {@link #discardBodyData()}. + * @return the amount of data written. + * @throws MalformedStreamException if the stream ends unexpectedly. + * @throws IOException if an i/o error occurs. + */ + public int readBodyData(final OutputStream output) + throws MalformedStreamException, IOException { + return (int) Streams.copy(newInputStream(), output, false); // N.B. Streams.copy closes the input stream + } + + /** + * Creates a new {@link ItemInputStream}. + * + * @return A new instance of {@link ItemInputStream}. + */ + public ItemInputStream newInputStream() { + return new ItemInputStream(); + } + + /** + *

Reads {@code body-data} from the current + * {@code encapsulation} and discards it. + * + *

Use this method to skip encapsulations you don't need or don't + * understand. + * + * @return The amount of data discarded. + * @throws MalformedStreamException if the stream ends unexpectedly. + * @throws IOException if an i/o error occurs. + */ + public int discardBodyData() throws MalformedStreamException, IOException { + return readBodyData(null); + } + + /** + * Finds the beginning of the first {@code encapsulation}. + * + * @return {@code true} if an {@code encapsulation} was found in + * the stream. + * @throws IOException if an i/o error occurs. + */ + public boolean skipPreamble() throws IOException { + // First delimiter may be not preceded with a CRLF. + System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2); + boundaryLength = boundary.length - 2; + computeBoundaryTable(); + try { + // Discard all data up to the delimiter. + discardBodyData(); + + // Read boundary - if succeeded, the stream contains an + // encapsulation. + return readBoundary(); + } catch (final MalformedStreamException e) { + return false; + } finally { + // Restore delimiter. + System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2); + boundaryLength = boundary.length; + boundary[0] = CR; + boundary[1] = LF; + computeBoundaryTable(); + } + } + + /** + * Compares {@code count} first bytes in the arrays + * {@code a} and {@code b}. + * + * @param a The first array to compare. + * @param b The second array to compare. + * @param count How many bytes should be compared. + * @return {@code true} if {@code count} first bytes in arrays + * {@code a} and {@code b} are equal. + */ + public static boolean arrayequals(final byte[] a, + final byte[] b, + final int count) { + for (int i = 0; i < count; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } + + /** + * Searches for a byte of specified value in the {@code buffer}, + * starting at the specified {@code position}. + * + * @param value The value to find. + * @param pos The starting position for searching. + * @return The position of byte found, counting from beginning of the + * {@code buffer}, or {@code -1} if not found. + */ + protected int findByte(final byte value, + final int pos) { + for (int i = pos; i < tail; i++) { + if (buffer[i] == value) { + return i; + } + } + + return -1; + } + + /** + * Searches for the {@code boundary} in the {@code buffer} + * region delimited by {@code head} and {@code tail}. + * + * @return The position of the boundary found, counting from the + * beginning of the {@code buffer}, or {@code -1} if + * not found. + */ + protected int findSeparator() { + + int bufferPos = this.head; + int tablePos = 0; + + while (bufferPos < this.tail) { + while (tablePos >= 0 && buffer[bufferPos] != boundary[tablePos]) { + tablePos = boundaryTable[tablePos]; + } + bufferPos++; + tablePos++; + if (tablePos == boundaryLength) { + return bufferPos - boundaryLength; + } + } + return -1; + } + + /** + * Thrown to indicate that the input stream fails to follow the + * required syntax. + */ + public static class MalformedStreamException extends IOException { + + /** + * The UID to use when serializing this instance. + */ + private static final long serialVersionUID = 6466926458059796677L; + + /** + * Constructs a {@code MalformedStreamException} with no + * detail message. + */ + public MalformedStreamException() { + } + + /** + * Constructs an {@code MalformedStreamException} with + * the specified detail message. + * + * @param message The detail message. + */ + public MalformedStreamException(final String message) { + super(message); + } + + } + + /** + * Thrown upon attempt of setting an invalid boundary token. + */ + public static class IllegalBoundaryException extends IOException { + + /** + * The UID to use when serializing this instance. + */ + private static final long serialVersionUID = -161533165102632918L; + + /** + * Constructs an {@code IllegalBoundaryException} with no + * detail message. + */ + public IllegalBoundaryException() { + } + + /** + * Constructs an {@code IllegalBoundaryException} with + * the specified detail message. + * + * @param message The detail message. + */ + public IllegalBoundaryException(final String message) { + super(message); + } + + } + + /** + * An {@link InputStream} for reading an items contents. + */ + public class ItemInputStream extends InputStream implements Closeable { + + /** + * The number of bytes, which have been read so far. + */ + private long total; + + /** + * The number of bytes, which must be hold, because + * they might be a part of the boundary. + */ + private int pad; + + /** + * The current offset in the buffer. + */ + private int pos; + + /** + * Whether the stream is already closed. + */ + private boolean closed; + + /** + * Creates a new instance. + */ + ItemInputStream() { + findSeparator(); + } + + /** + * Called for finding the separator. + */ + private void findSeparator() { + pos = MultipartStream.this.findSeparator(); + if (pos == -1) { + if (tail - head > keepRegion) { + pad = keepRegion; + } else { + pad = tail - head; + } + } + } + + /** + * Returns the number of bytes, which have been read + * by the stream. + * + * @return Number of bytes, which have been read so far. + */ + public long getBytesRead() { + return total; + } + + /** + * Returns the number of bytes, which are currently + * available, without blocking. + * + * @return Number of bytes in the buffer. + * @throws IOException An I/O error occurs. + */ + @Override + public int available() throws IOException { + if (pos == -1) { + return tail - head - pad; + } + return pos - head; + } + + /** + * Offset when converting negative bytes to integers. + */ + private static final int BYTE_POSITIVE_OFFSET = 256; + + /** + * Returns the next byte in the stream. + * + * @return The next byte in the stream, as a non-negative + * integer, or -1 for EOF. + * @throws IOException An I/O error occurred. + */ + @Override + public int read() throws IOException { + if (closed) { + throw new FileItemStream.ItemSkippedException(); + } + if (available() == 0 && makeAvailable() == 0) { + return -1; + } + ++total; + final int b = buffer[head++]; + if (b >= 0) { + return b; + } + return b + BYTE_POSITIVE_OFFSET; + } + + /** + * Reads bytes into the given buffer. + * + * @param b The destination buffer, where to write to. + * @param off Offset of the first byte in the buffer. + * @param len Maximum number of bytes to read. + * @return Number of bytes, which have been actually read, + * or -1 for EOF. + * @throws IOException An I/O error occurred. + */ + @Override + public int read(final byte[] b, final int off, final int len) throws IOException { + if (closed) { + throw new FileItemStream.ItemSkippedException(); + } + if (len == 0) { + return 0; + } + int res = available(); + if (res == 0) { + res = makeAvailable(); + if (res == 0) { + return -1; + } + } + res = Math.min(res, len); + System.arraycopy(buffer, head, b, off, res); + head += res; + total += res; + return res; + } + + /** + * Closes the input stream. + * + * @throws IOException An I/O error occurred. + */ + @Override + public void close() throws IOException { + close(false); + } + + /** + * Closes the input stream. + * + * @param pCloseUnderlying Whether to close the underlying stream + * (hard close) + * @throws IOException An I/O error occurred. + */ + public void close(final boolean pCloseUnderlying) throws IOException { + if (closed) { + return; + } + if (pCloseUnderlying) { + closed = true; + input.close(); + } else { + for (; ; ) { + int av = available(); + if (av == 0) { + av = makeAvailable(); + if (av == 0) { + break; + } + } + skip(av); + } + } + closed = true; + } + + /** + * Skips the given number of bytes. + * + * @param bytes Number of bytes to skip. + * @return The number of bytes, which have actually been + * skipped. + * @throws IOException An I/O error occurred. + */ + @Override + public long skip(final long bytes) throws IOException { + if (closed) { + throw new FileItemStream.ItemSkippedException(); + } + int av = available(); + if (av == 0) { + av = makeAvailable(); + if (av == 0) { + return 0; + } + } + final long res = Math.min(av, bytes); + head += res; + return res; + } + + /** + * Attempts to read more data. + * + * @return Number of available bytes + * @throws IOException An I/O error occurred. + */ + private int makeAvailable() throws IOException { + if (pos != -1) { + return 0; + } + + // Move the data to the beginning of the buffer. + total += tail - head - pad; + System.arraycopy(buffer, tail - pad, buffer, 0, pad); + + // Refill buffer with new data. + head = 0; + tail = pad; + + for (; ; ) { + final int bytesRead = input.read(buffer, tail, bufSize - tail); + if (bytesRead == -1) { + // The last pad amount is left in the buffer. + // Boundary can't be in there so signal an error + // condition. + final String msg = "Stream ended unexpectedly"; + throw new MalformedStreamException(msg); + } + if (notifier != null) { + notifier.noteBytesRead(bytesRead); + } + tail += bytesRead; + + findSeparator(); + final int av = available(); + + if (av > 0 || pos != -1) { + return av; + } + } + } + + /** + * Returns, whether the stream is closed. + * + * @return True, if the stream is closed, otherwise false. + */ + @Override + public boolean isClosed() { + return closed; + } + + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/ParameterParser.java b/client/src/test/java/org/apache/commons/fileupload2/ParameterParser.java new file mode 100644 index 0000000000..bd00a9a11b --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/ParameterParser.java @@ -0,0 +1,333 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +import org.apache.commons.fileupload2.util.mime.MimeUtility; +import org.apache.commons.fileupload2.util.mime.RFC2231Utility; + +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * A simple parser intended to parse sequences of name/value pairs. + *

+ * Parameter values are expected to be enclosed in quotes if they + * contain unsafe characters, such as '=' characters or separators. + * Parameter values are optional and can be omitted. + * + *

+ * {@code param1 = value; param2 = "anything goes; really"; param3} + *

+ */ +public class ParameterParser { + + /** + * String to be parsed. + */ + private char[] chars = null; + + /** + * Current position in the string. + */ + private int pos = 0; + + /** + * Maximum position in the string. + */ + private int len = 0; + + /** + * Start of a token. + */ + private int i1 = 0; + + /** + * End of a token. + */ + private int i2 = 0; + + /** + * Whether names stored in the map should be converted to lower case. + */ + private boolean lowerCaseNames = false; + + /** + * Default ParameterParser constructor. + */ + public ParameterParser() { + } + + /** + * Are there any characters left to parse? + * + * @return {@code true} if there are unparsed characters, + * {@code false} otherwise. + */ + private boolean hasChar() { + return this.pos < this.len; + } + + /** + * A helper method to process the parsed token. This method removes + * leading and trailing blanks as well as enclosing quotation marks, + * when necessary. + * + * @param quoted {@code true} if quotation marks are expected, + * {@code false} otherwise. + * @return the token + */ + private String getToken(final boolean quoted) { + // Trim leading white spaces + while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) { + i1++; + } + // Trim trailing white spaces + while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) { + i2--; + } + // Strip away quotation marks if necessary + if (quoted + && ((i2 - i1) >= 2) + && (chars[i1] == '"') + && (chars[i2 - 1] == '"')) { + i1++; + i2--; + } + String result = null; + if (i2 > i1) { + result = new String(chars, i1, i2 - i1); + } + return result; + } + + /** + * Tests if the given character is present in the array of characters. + * + * @param ch the character to test for presence in the array of characters + * @param charray the array of characters to test against + * @return {@code true} if the character is present in the array of + * characters, {@code false} otherwise. + */ + private boolean isOneOf(final char ch, final char[] charray) { + boolean result = false; + for (final char element : charray) { + if (ch == element) { + result = true; + break; + } + } + return result; + } + + /** + * Parses out a token until any of the given terminators + * is encountered. + * + * @param terminators the array of terminating characters. Any of these + * characters when encountered signify the end of the token + * @return the token + */ + private String parseToken(final char[] terminators) { + char ch; + i1 = pos; + i2 = pos; + while (hasChar()) { + ch = chars[pos]; + if (isOneOf(ch, terminators)) { + break; + } + i2++; + pos++; + } + return getToken(false); + } + + /** + * Parses out a token until any of the given terminators + * is encountered outside the quotation marks. + * + * @param terminators the array of terminating characters. Any of these + * characters when encountered outside the quotation marks signify the end + * of the token + * @return the token + */ + private String parseQuotedToken(final char[] terminators) { + char ch; + i1 = pos; + i2 = pos; + boolean quoted = false; + boolean charEscaped = false; + while (hasChar()) { + ch = chars[pos]; + if (!quoted && isOneOf(ch, terminators)) { + break; + } + if (!charEscaped && ch == '"') { + quoted = !quoted; + } + charEscaped = (!charEscaped && ch == '\\'); + i2++; + pos++; + + } + return getToken(true); + } + + /** + * Returns {@code true} if parameter names are to be converted to lower + * case when name/value pairs are parsed. + * + * @return {@code true} if parameter names are to be + * converted to lower case when name/value pairs are parsed. + * Otherwise returns {@code false} + */ + public boolean isLowerCaseNames() { + return this.lowerCaseNames; + } + + /** + * Sets the flag if parameter names are to be converted to lower case when + * name/value pairs are parsed. + * + * @param b {@code true} if parameter names are to be + * converted to lower case when name/value pairs are parsed. + * {@code false} otherwise. + */ + public void setLowerCaseNames(final boolean b) { + this.lowerCaseNames = b; + } + + /** + * Extracts a map of name/value pairs from the given string. Names are + * expected to be unique. Multiple separators may be specified and + * the earliest found in the input string is used. + * + * @param str the string that contains a sequence of name/value pairs + * @param separators the name/value pairs separators + * @return a map of name/value pairs + */ + public Map parse(final String str, final char[] separators) { + if (separators == null || separators.length == 0) { + return new HashMap<>(); + } + char separator = separators[0]; + if (str != null) { + int idx = str.length(); + for (final char separator2 : separators) { + final int tmp = str.indexOf(separator2); + if (tmp != -1 && tmp < idx) { + idx = tmp; + separator = separator2; + } + } + } + return parse(str, separator); + } + + /** + * Extracts a map of name/value pairs from the given string. Names are + * expected to be unique. + * + * @param str the string that contains a sequence of name/value pairs + * @param separator the name/value pairs separator + * @return a map of name/value pairs + */ + public Map parse(final String str, final char separator) { + if (str == null) { + return new HashMap<>(); + } + return parse(str.toCharArray(), separator); + } + + /** + * Extracts a map of name/value pairs from the given array of + * characters. Names are expected to be unique. + * + * @param charArray the array of characters that contains a sequence of + * name/value pairs + * @param separator the name/value pairs separator + * @return a map of name/value pairs + */ + public Map parse(final char[] charArray, final char separator) { + if (charArray == null) { + return new HashMap<>(); + } + return parse(charArray, 0, charArray.length, separator); + } + + /** + * Extracts a map of name/value pairs from the given array of + * characters. Names are expected to be unique. + * + * @param charArray the array of characters that contains a sequence of + * name/value pairs + * @param offset - the initial offset. + * @param length - the length. + * @param separator the name/value pairs separator + * @return a map of name/value pairs + */ + public Map parse( + final char[] charArray, + final int offset, + final int length, + final char separator) { + + if (charArray == null) { + return new HashMap<>(); + } + final HashMap params = new HashMap<>(); + this.chars = charArray.clone(); + this.pos = offset; + this.len = length; + + String paramName; + String paramValue; + while (hasChar()) { + paramName = parseToken(new char[]{ + '=', separator}); + paramValue = null; + if (hasChar() && (charArray[pos] == '=')) { + pos++; // skip '=' + paramValue = parseQuotedToken(new char[]{ + separator}); + + if (paramValue != null) { + try { + paramValue = RFC2231Utility.hasEncodedValue(paramName) ? RFC2231Utility.decodeText(paramValue) + : MimeUtility.decodeText(paramValue); + } catch (final UnsupportedEncodingException e) { + // let's keep the original value in this case + } + } + } + if (hasChar() && (charArray[pos] == separator)) { + pos++; // skip separator + } + if ((paramName != null) && !paramName.isEmpty()) { + paramName = RFC2231Utility.stripDelimiter(paramName); + if (this.lowerCaseNames) { + paramName = paramName.toLowerCase(Locale.ENGLISH); + } + params.put(paramName, paramValue); + } + } + return params; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/ProgressListener.java b/client/src/test/java/org/apache/commons/fileupload2/ProgressListener.java new file mode 100644 index 0000000000..7f33ec8f28 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/ProgressListener.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +/** + * The {@link ProgressListener} may be used to display a progress bar + * or do stuff like that. + */ +public interface ProgressListener { + + /** + * Updates the listeners status information. + * + * @param pBytesRead The total number of bytes, which have been read + * so far. + * @param pContentLength The total number of bytes, which are being + * read. May be -1, if this number is unknown. + * @param pItems The number of the field, which is currently being + * read. (0 = no item so far, 1 = first item is being read, ...) + */ + void update(long pBytesRead, long pContentLength, int pItems); + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/RequestContext.java b/client/src/test/java/org/apache/commons/fileupload2/RequestContext.java new file mode 100644 index 0000000000..8cb590faff --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/RequestContext.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +import java.io.IOException; +import java.io.InputStream; + +/** + *

Abstracts access to the request information needed for file uploads. This + * interface should be implemented for each type of request that may be + * handled by FileUpload, such as servlets and portlets.

+ * + * @since 1.1 + */ +public interface RequestContext { + + /** + * Retrieve the character encoding for the request. + * + * @return The character encoding for the request. + */ + String getCharacterEncoding(); + + /** + * Retrieve the content type of the request. + * + * @return The content type of the request. + */ + String getContentType(); + + /** + * Retrieve the content length of the request. + * + * @return The content length of the request. + * @deprecated 1.3 Use {@link UploadContext#contentLength()} instead + */ + @Deprecated + int getContentLength(); + + /** + * Retrieve the input stream for the request. + * + * @return The input stream for the request. + * @throws IOException if a problem occurs. + */ + InputStream getInputStream() throws IOException; + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/UploadContext.java b/client/src/test/java/org/apache/commons/fileupload2/UploadContext.java new file mode 100644 index 0000000000..181ac7b1c9 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/UploadContext.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2; + +/** + * Enhanced access to the request information needed for file uploads, + * which fixes the Content Length data access in {@link RequestContext}. + *

+ * The reason of introducing this new interface is just for backward compatibility + * and it might vanish for a refactored 2.x version moving the new method into + * RequestContext again. + * + * @since 1.3 + */ +public interface UploadContext extends RequestContext { + + /** + * Retrieve the content length of the request. + * + * @return The content length of the request. + * @since 1.3 + */ + long contentLength(); + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/disk/DiskFileItem.java b/client/src/test/java/org/apache/commons/fileupload2/disk/DiskFileItem.java new file mode 100644 index 0000000000..1f68478c3e --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/disk/DiskFileItem.java @@ -0,0 +1,615 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.disk; + +import org.apache.commons.fileupload2.FileItem; +import org.apache.commons.fileupload2.FileItemHeaders; +import org.apache.commons.fileupload2.FileUploadException; +import org.apache.commons.fileupload2.ParameterParser; +import org.apache.commons.fileupload2.util.Streams; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.DeferredFileOutputStream; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.lang.String.format; + +/** + *

The default implementation of the + * {@link FileItem FileItem} interface. + * + *

After retrieving an instance of this class from a {@link + * DiskFileItemFactory} instance (see + * {@link org.apache.commons.fileupload2.servlet.ServletFileUpload + * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may + * either request all contents of file at once using {@link #get()} or + * request an {@link InputStream InputStream} with + * {@link #getInputStream()} and process the file without attempting to load + * it into memory, which may come handy with large files. + * + *

Temporary files, which are created for file items, should be + * deleted later on. The best way to do this is using a + * {@link org.apache.commons.io.FileCleaningTracker}, which you can set on the + * {@link DiskFileItemFactory}. However, if you do use such a tracker, + * then you must consider the following: Temporary files are automatically + * deleted as soon as they are no longer needed. (More precisely, when the + * corresponding instance of {@link File} is garbage collected.) + * This is done by the so-called reaper thread, which is started and stopped + * automatically by the {@link org.apache.commons.io.FileCleaningTracker} when + * there are files to be tracked. + * It might make sense to terminate that thread, for example, if + * your web application ends. See the section on "Resource cleanup" + * in the users guide of commons-fileupload.

+ * + * @since 1.1 + */ +public class DiskFileItem + implements FileItem { + + // ----------------------------------------------------- Manifest constants + + /** + * Default content charset to be used when no explicit charset + * parameter is provided by the sender. Media subtypes of the + * "text" type are defined to have a default charset value of + * "ISO-8859-1" when received via HTTP. + */ + public static final String DEFAULT_CHARSET = "ISO-8859-1"; + + // ----------------------------------------------------------- Data members + + /** + * UID used in unique file name generation. + */ + private static final String UID = + UUID.randomUUID().toString().replace('-', '_'); + + /** + * Counter used in unique identifier generation. + */ + private static final AtomicInteger COUNTER = new AtomicInteger(0); + + /** + * The name of the form field as provided by the browser. + */ + private String fieldName; + + /** + * The content type passed by the browser, or {@code null} if + * not defined. + */ + private final String contentType; + + /** + * Whether or not this item is a simple form field. + */ + private boolean isFormField; + + /** + * The original file name in the user's file system. + */ + private final String fileName; + + /** + * The size of the item, in bytes. This is used to cache the size when a + * file item is moved from its original location. + */ + private long size = -1; + + + /** + * The threshold above which uploads will be stored on disk. + */ + private final int sizeThreshold; + + /** + * The directory in which uploaded files will be stored, if stored on disk. + */ + private final File repository; + + /** + * Cached contents of the file. + */ + private byte[] cachedContent; + + /** + * Output stream for this item. + */ + private transient DeferredFileOutputStream dfos; + + /** + * The temporary file to use. + */ + private transient File tempFile; + + /** + * The file items headers. + */ + private FileItemHeaders headers; + + /** + * Default content charset to be used when no explicit charset + * parameter is provided by the sender. + */ + private String defaultCharset = DEFAULT_CHARSET; + + // ----------------------------------------------------------- Constructors + + /** + * Constructs a new {@code DiskFileItem} instance. + * + * @param fieldName The name of the form field. + * @param contentType The content type passed by the browser or + * {@code null} if not specified. + * @param isFormField Whether or not this item is a plain form field, as + * opposed to a file upload. + * @param fileName The original file name in the user's file system, or + * {@code null} if not specified. + * @param sizeThreshold The threshold, in bytes, below which items will be + * retained in memory and above which they will be + * stored as a file. + * @param repository The data repository, which is the directory in + * which files will be created, should the item size + * exceed the threshold. + */ + public DiskFileItem(final String fieldName, + final String contentType, final boolean isFormField, final String fileName, + final int sizeThreshold, final File repository) { + this.fieldName = fieldName; + this.contentType = contentType; + this.isFormField = isFormField; + this.fileName = fileName; + this.sizeThreshold = sizeThreshold; + this.repository = repository; + } + + // ------------------------------- Methods from javax.activation.DataSource + + /** + * Returns an {@link InputStream InputStream} that can be + * used to retrieve the contents of the file. + * + * @return An {@link InputStream InputStream} that can be + * used to retrieve the contents of the file. + * @throws IOException if an error occurs. + */ + @Override + public InputStream getInputStream() + throws IOException { + if (!isInMemory()) { + return Files.newInputStream(dfos.getFile().toPath()); + } + + if (cachedContent == null) { + cachedContent = dfos.getData(); + } + return new ByteArrayInputStream(cachedContent); + } + + /** + * Returns the content type passed by the agent or {@code null} if + * not defined. + * + * @return The content type passed by the agent or {@code null} if + * not defined. + */ + @Override + public String getContentType() { + return contentType; + } + + /** + * Returns the content charset passed by the agent or {@code null} if + * not defined. + * + * @return The content charset passed by the agent or {@code null} if + * not defined. + */ + public String getCharSet() { + final ParameterParser parser = new ParameterParser(); + parser.setLowerCaseNames(true); + // Parameter parser can handle null input + final Map params = parser.parse(getContentType(), ';'); + return params.get("charset"); + } + + /** + * Returns the original file name in the client's file system. + * + * @return The original file name in the client's file system. + * @throws org.apache.commons.fileupload2.InvalidFileNameException The file name contains a NUL character, + * which might be an indicator of a security attack. If you intend to + * use the file name anyways, catch the exception and use + * {@link org.apache.commons.fileupload2.InvalidFileNameException#getName()}. + */ + @Override + public String getName() { + return Streams.checkFileName(fileName); + } + + // ------------------------------------------------------- FileItem methods + + /** + * Provides a hint as to whether or not the file contents will be read + * from memory. + * + * @return {@code true} if the file contents will be read + * from memory; {@code false} otherwise. + */ + @Override + public boolean isInMemory() { + if (cachedContent != null) { + return true; + } + return dfos.isInMemory(); + } + + /** + * Returns the size of the file. + * + * @return The size of the file, in bytes. + */ + @Override + public long getSize() { + if (size >= 0) { + return size; + } + if (cachedContent != null) { + return cachedContent.length; + } + if (dfos.isInMemory()) { + return dfos.getData().length; + } + return dfos.getFile().length(); + } + + /** + * Returns the contents of the file as an array of bytes. If the + * contents of the file were not yet cached in memory, they will be + * loaded from the disk storage and cached. + * + * @return The contents of the file as an array of bytes + * or {@code null} if the data cannot be read + * @throws UncheckedIOException if an I/O error occurs + */ + @Override + public byte[] get() throws UncheckedIOException { + if (isInMemory()) { + if (cachedContent == null && dfos != null) { + cachedContent = dfos.getData(); + } + return cachedContent != null ? cachedContent.clone() : new byte[0]; + } + + final byte[] fileData = new byte[(int) getSize()]; + + try (InputStream fis = Files.newInputStream(dfos.getFile().toPath())) { + IOUtils.readFully(fis, fileData); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + return fileData; + } + + /** + * Returns the contents of the file as a String, using the specified + * encoding. This method uses {@link #get()} to retrieve the + * contents of the file. + * + * @param charset The charset to use. + * @return The contents of the file, as a string. + * @throws UnsupportedEncodingException if the requested character + * encoding is not available. + */ + @Override + public String getString(final String charset) + throws UnsupportedEncodingException, IOException { + return new String(get(), charset); + } + + /** + * Returns the contents of the file as a String, using the default + * character encoding. This method uses {@link #get()} to retrieve the + * contents of the file. + * + * TODO Consider making this method throw UnsupportedEncodingException. + * + * @return The contents of the file, as a string. + */ + @Override + public String getString() { + try { + final byte[] rawData = get(); + String charset = getCharSet(); + if (charset == null) { + charset = defaultCharset; + } + return new String(rawData, charset); + } catch (final IOException e) { + return ""; + } + } + + /** + * A convenience method to write an uploaded item to disk. The client code + * is not concerned with whether or not the item is stored in memory, or on + * disk in a temporary location. They just want to write the uploaded item + * to a file. + *

+ * This implementation first attempts to rename the uploaded item to the + * specified destination file, if the item was originally written to disk. + * Otherwise, the data will be copied to the specified file. + *

+ * This method is only guaranteed to work once, the first time it + * is invoked for a particular item. This is because, in the event that the + * method renames a temporary file, that file will no longer be available + * to copy or rename again at a later time. + * + * @param file The {@code File} into which the uploaded item should + * be stored. + * @throws Exception if an error occurs. + */ + @Override + public void write(final File file) throws Exception { + if (isInMemory()) { + try (OutputStream fout = Files.newOutputStream(file.toPath())) { + fout.write(get()); + } catch (final IOException e) { + throw new IOException("Unexpected output data"); + } + } else { + final File outputFile = getStoreLocation(); + if (outputFile == null) { + /* + * For whatever reason we cannot write the + * file to disk. + */ + throw new FileUploadException( + "Cannot write uploaded file to disk!"); + } + // Save the length of the file + size = outputFile.length(); + /* + * The uploaded file is being stored on disk + * in a temporary location so move it to the + * desired file. + */ + if (file.exists() && !file.delete()) { + throw new FileUploadException( + "Cannot write uploaded file to disk!"); + } + FileUtils.moveFile(outputFile, file); + } + } + + /** + * Deletes the underlying storage for a file item, including deleting any associated temporary disk file. + * This method can be used to ensure that this is done at an earlier time, thus preserving system resources. + */ + @Override + public void delete() { + cachedContent = null; + final File outputFile = getStoreLocation(); + if (outputFile != null && !isInMemory() && outputFile.exists()) { + if (!outputFile.delete()) { + final String desc = "Cannot delete " + outputFile.toString(); + throw new UncheckedIOException(desc, new IOException(desc)); + } + } + } + + /** + * Returns the name of the field in the multipart form corresponding to + * this file item. + * + * @return The name of the form field. + * @see #setFieldName(String) + */ + @Override + public String getFieldName() { + return fieldName; + } + + /** + * Sets the field name used to reference this file item. + * + * @param fieldName The name of the form field. + * @see #getFieldName() + */ + @Override + public void setFieldName(final String fieldName) { + this.fieldName = fieldName; + } + + /** + * Determines whether or not a {@code FileItem} instance represents + * a simple form field. + * + * @return {@code true} if the instance represents a simple form + * field; {@code false} if it represents an uploaded file. + * @see #setFormField(boolean) + */ + @Override + public boolean isFormField() { + return isFormField; + } + + /** + * Specifies whether or not a {@code FileItem} instance represents + * a simple form field. + * + * @param state {@code true} if the instance represents a simple form + * field; {@code false} if it represents an uploaded file. + * @see #isFormField() + */ + @Override + public void setFormField(final boolean state) { + isFormField = state; + } + + /** + * Returns an {@link OutputStream OutputStream} that can + * be used for storing the contents of the file. + * + * @return An {@link OutputStream OutputStream} that can be used + * for storing the contents of the file. + */ + @Override + public OutputStream getOutputStream() { + if (dfos == null) { + final File outputFile = getTempFile(); + dfos = new DeferredFileOutputStream(sizeThreshold, outputFile); + } + return dfos; + } + + // --------------------------------------------------------- Public methods + + /** + * Returns the {@link File} object for the {@code FileItem}'s + * data's temporary location on the disk. Note that for + * {@code FileItem}s that have their data stored in memory, + * this method will return {@code null}. When handling large + * files, you can use {@link File#renameTo(File)} to + * move the file to new location without copying the data, if the + * source and destination locations reside within the same logical + * volume. + * + * @return The data file, or {@code null} if the data is stored in + * memory. + */ + public File getStoreLocation() { + if (dfos == null) { + return null; + } + if (isInMemory()) { + return null; + } + return dfos.getFile(); + } + + // ------------------------------------------------------ Protected methods + + /** + * Creates and returns a {@link File File} representing a uniquely + * named temporary file in the configured repository path. The lifetime of + * the file is tied to the lifetime of the {@code FileItem} instance; + * the file will be deleted when the instance is garbage collected. + *

+ * Note: Subclasses that override this method must ensure that they return the + * same File each time. + * + * @return The {@link File File} to be used for temporary storage. + */ + protected File getTempFile() { + if (tempFile == null) { + File tempDir = repository; + if (tempDir == null) { + tempDir = new File(System.getProperty("java.io.tmpdir")); + } + + final String tempFileName = format("upload_%s_%s.tmp", UID, getUniqueId()); + + tempFile = new File(tempDir, tempFileName); + } + return tempFile; + } + + // -------------------------------------------------------- Private methods + + /** + * Returns an identifier that is unique within the class loader used to + * load this class, but does not have random-like appearance. + * + * @return A String with the non-random looking instance identifier. + */ + private static String getUniqueId() { + final int limit = 100000000; + final int current = COUNTER.getAndIncrement(); + String id = Integer.toString(current); + + // If you manage to get more than 100 million of ids, you'll + // start getting ids longer than 8 characters. + if (current < limit) { + id = ("00000000" + id).substring(id.length()); + } + return id; + } + + /** + * Returns a string representation of this object. + * + * @return a string representation of this object. + */ + @Override + public String toString() { + return format("name=%s, StoreLocation=%s, size=%s bytes, isFormField=%s, FieldName=%s", + getName(), getStoreLocation(), getSize(), + isFormField(), getFieldName()); + } + + /** + * Returns the file item headers. + * + * @return The file items headers. + */ + @Override + public FileItemHeaders getHeaders() { + return headers; + } + + /** + * Sets the file item headers. + * + * @param pHeaders The file items headers. + */ + @Override + public void setHeaders(final FileItemHeaders pHeaders) { + headers = pHeaders; + } + + /** + * Returns the default charset for use when no explicit charset + * parameter is provided by the sender. + * + * @return the default charset + */ + public String getDefaultCharset() { + return defaultCharset; + } + + /** + * Sets the default charset for use when no explicit charset + * parameter is provided by the sender. + * + * @param charset the default charset + */ + public void setDefaultCharset(final String charset) { + defaultCharset = charset; + } +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/disk/DiskFileItemFactory.java b/client/src/test/java/org/apache/commons/fileupload2/disk/DiskFileItemFactory.java new file mode 100644 index 0000000000..56d8154dee --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/disk/DiskFileItemFactory.java @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.disk; + +import org.apache.commons.fileupload2.FileItem; +import org.apache.commons.fileupload2.FileItemFactory; +import org.apache.commons.io.FileCleaningTracker; + +import java.io.File; + +/** + *

The default {@link FileItemFactory} + * implementation. This implementation creates + * {@link FileItem} instances which keep their + * content either in memory, for smaller items, or in a temporary file on disk, + * for larger items. The size threshold, above which content will be stored on + * disk, is configurable, as is the directory in which temporary files will be + * created.

+ * + *

If not otherwise configured, the default configuration values are as + * follows:

+ *
    + *
  • Size threshold is 10KB.
  • + *
  • Repository is the system default temp directory, as returned by + * {@code System.getProperty("java.io.tmpdir")}.
  • + *
+ *

+ * NOTE: Files are created in the system default temp directory with + * predictable names. This means that a local attacker with write access to that + * directory can perform a TOUTOC attack to replace any uploaded file with a + * file of the attackers choice. The implications of this will depend on how the + * uploaded file is used but could be significant. When using this + * implementation in an environment with local, untrusted users, + * {@link #setRepository(File)} MUST be used to configure a repository location + * that is not publicly writable. In a Servlet container the location identified + * by the ServletContext attribute {@code javax.servlet.context.tempdir} + * may be used. + *

+ * + *

Temporary files, which are created for file items, should be + * deleted later on. The best way to do this is using a + * {@link FileCleaningTracker}, which you can set on the + * {@link DiskFileItemFactory}. However, if you do use such a tracker, + * then you must consider the following: Temporary files are automatically + * deleted as soon as they are no longer needed. (More precisely, when the + * corresponding instance of {@link File} is garbage collected.) + * This is done by the so-called reaper thread, which is started and stopped + * automatically by the {@link FileCleaningTracker} when there are files to be + * tracked. + * It might make sense to terminate that thread, for example, if + * your web application ends. See the section on "Resource cleanup" + * in the users guide of commons-fileupload.

+ * + * @since 1.1 + */ +public class DiskFileItemFactory implements FileItemFactory { + + // ----------------------------------------------------- Manifest constants + + /** + * The default threshold above which uploads will be stored on disk. + */ + public static final int DEFAULT_SIZE_THRESHOLD = 10240; + + // ----------------------------------------------------- Instance Variables + + /** + * The directory in which uploaded files will be stored, if stored on disk. + */ + private File repository; + + /** + * The threshold above which uploads will be stored on disk. + */ + private int sizeThreshold = DEFAULT_SIZE_THRESHOLD; + + /** + *

The instance of {@link FileCleaningTracker}, which is responsible + * for deleting temporary files.

+ *

May be null, if tracking files is not required.

+ */ + private FileCleaningTracker fileCleaningTracker; + + /** + * Default content charset to be used when no explicit charset + * parameter is provided by the sender. + */ + private String defaultCharset = DiskFileItem.DEFAULT_CHARSET; + + // ----------------------------------------------------------- Constructors + + /** + * Constructs an unconfigured instance of this class. The resulting factory + * may be configured by calling the appropriate setter methods. + */ + public DiskFileItemFactory() { + this(DEFAULT_SIZE_THRESHOLD, null); + } + + /** + * Constructs a preconfigured instance of this class. + * + * @param sizeThreshold The threshold, in bytes, below which items will be + * retained in memory and above which they will be + * stored as a file. + * @param repository The data repository, which is the directory in + * which files will be created, should the item size + * exceed the threshold. + */ + public DiskFileItemFactory(final int sizeThreshold, final File repository) { + this.sizeThreshold = sizeThreshold; + this.repository = repository; + } + + // ------------------------------------------------------------- Properties + + /** + * Returns the directory used to temporarily store files that are larger + * than the configured size threshold. + * + * @return The directory in which temporary files will be located. + * @see #setRepository(File) + */ + public File getRepository() { + return repository; + } + + /** + * Sets the directory used to temporarily store files that are larger + * than the configured size threshold. + * + * @param repository The directory in which temporary files will be located. + * @see #getRepository() + */ + public void setRepository(final File repository) { + this.repository = repository; + } + + /** + * Returns the size threshold beyond which files are written directly to + * disk. The default value is 10240 bytes. + * + * @return The size threshold, in bytes. + * @see #setSizeThreshold(int) + */ + public int getSizeThreshold() { + return sizeThreshold; + } + + /** + * Sets the size threshold beyond which files are written directly to disk. + * + * @param sizeThreshold The size threshold, in bytes. + * @see #getSizeThreshold() + */ + public void setSizeThreshold(final int sizeThreshold) { + this.sizeThreshold = sizeThreshold; + } + + // --------------------------------------------------------- Public Methods + + /** + * Create a new {@link DiskFileItem} + * instance from the supplied parameters and the local factory + * configuration. + * + * @param fieldName The name of the form field. + * @param contentType The content type of the form field. + * @param isFormField {@code true} if this is a plain form field; + * {@code false} otherwise. + * @param fileName The name of the uploaded file, if any, as supplied + * by the browser or other client. + * @return The newly created file item. + */ + @Override + public FileItem createItem(final String fieldName, final String contentType, + final boolean isFormField, final String fileName) { + final DiskFileItem result = new DiskFileItem(fieldName, contentType, + isFormField, fileName, sizeThreshold, repository); + result.setDefaultCharset(defaultCharset); + final FileCleaningTracker tracker = getFileCleaningTracker(); + if (tracker != null) { + tracker.track(result.getTempFile(), result); + } + return result; + } + + /** + * Returns the tracker, which is responsible for deleting temporary + * files. + * + * @return An instance of {@link FileCleaningTracker}, or null + * (default), if temporary files aren't tracked. + */ + public FileCleaningTracker getFileCleaningTracker() { + return fileCleaningTracker; + } + + /** + * Sets the tracker, which is responsible for deleting temporary + * files. + * + * @param pTracker An instance of {@link FileCleaningTracker}, + * which will from now on track the created files, or null + * (default), to disable tracking. + */ + public void setFileCleaningTracker(final FileCleaningTracker pTracker) { + fileCleaningTracker = pTracker; + } + + /** + * Returns the default charset for use when no explicit charset + * parameter is provided by the sender. + * + * @return the default charset + */ + public String getDefaultCharset() { + return defaultCharset; + } + + /** + * Sets the default charset for use when no explicit charset + * parameter is provided by the sender. + * + * @param pCharset the default charset + */ + public void setDefaultCharset(final String pCharset) { + defaultCharset = pCharset; + } +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/disk/package-info.java b/client/src/test/java/org/apache/commons/fileupload2/disk/package-info.java new file mode 100644 index 0000000000..1d2caecb9b --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/disk/package-info.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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. + */ + +/** + *

+ * A disk-based implementation of the + * {@link org.apache.commons.fileupload2.FileItem FileItem} + * interface. This implementation retains smaller items in memory, while + * writing larger ones to disk. The threshold between these two is + * configurable, as is the location of files that are written to disk. + *

+ *

+ * In typical usage, an instance of + * {@link org.apache.commons.fileupload2.disk.DiskFileItemFactory DiskFileItemFactory} + * would be created, configured, and then passed to a + * {@link org.apache.commons.fileupload2.FileUpload FileUpload} + * implementation such as + * {@link org.apache.commons.fileupload2.servlet.ServletFileUpload ServletFileUpload} + * or + * {@link org.apache.commons.fileupload2.portlet.PortletFileUpload PortletFileUpload}. + *

+ *

+ * The following code fragment demonstrates this usage. + *

+ *
+ *        DiskFileItemFactory factory = new DiskFileItemFactory();
+ *        // maximum size that will be stored in memory
+ *        factory.setSizeThreshold(4096);
+ *        // the location for saving data that is larger than getSizeThreshold()
+ *        factory.setRepository(new File("/tmp"));
+ *
+ *        ServletFileUpload upload = new ServletFileUpload(factory);
+ * 
+ *

+ * Please see the FileUpload + * User Guide + * for further details and examples of how to use this package. + *

+ */ +package org.apache.commons.fileupload2.disk; diff --git a/client/src/test/java/org/apache/commons/fileupload2/impl/FileItemIteratorImpl.java b/client/src/test/java/org/apache/commons/fileupload2/impl/FileItemIteratorImpl.java new file mode 100644 index 0000000000..bd29bb87f9 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/impl/FileItemIteratorImpl.java @@ -0,0 +1,362 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.impl; + +import org.apache.commons.fileupload2.FileItem; +import org.apache.commons.fileupload2.FileItemHeaders; +import org.apache.commons.fileupload2.FileItemIterator; +import org.apache.commons.fileupload2.FileItemStream; +import org.apache.commons.fileupload2.FileUploadBase; +import org.apache.commons.fileupload2.FileUploadException; +import org.apache.commons.fileupload2.MultipartStream; +import org.apache.commons.fileupload2.ProgressListener; +import org.apache.commons.fileupload2.RequestContext; +import org.apache.commons.fileupload2.UploadContext; +import org.apache.commons.fileupload2.pub.FileUploadIOException; +import org.apache.commons.fileupload2.pub.InvalidContentTypeException; +import org.apache.commons.fileupload2.pub.SizeLimitExceededException; +import org.apache.commons.fileupload2.util.LimitedInputStream; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.NoSuchElementException; +import java.util.Objects; + +import static java.lang.String.format; + +/** + * The iterator, which is returned by + * {@link FileUploadBase#getItemIterator(RequestContext)}. + */ +public class FileItemIteratorImpl implements FileItemIterator { + /** + * The file uploads processing utility. + * + * @see FileUploadBase + */ + private final FileUploadBase fileUploadBase; + /** + * The request context. + * + * @see RequestContext + */ + private final RequestContext ctx; + /** + * The maximum allowed size of a complete request. + */ + private long sizeMax; + /** + * The maximum allowed size of a single uploaded file. + */ + private long fileSizeMax; + + + @Override + public long getSizeMax() { + return sizeMax; + } + + @Override + public void setSizeMax(final long sizeMax) { + this.sizeMax = sizeMax; + } + + @Override + public long getFileSizeMax() { + return fileSizeMax; + } + + @Override + public void setFileSizeMax(final long fileSizeMax) { + this.fileSizeMax = fileSizeMax; + } + + /** + * The multi part stream to process. + */ + private MultipartStream multiPartStream; + + /** + * The notifier, which used for triggering the + * {@link ProgressListener}. + */ + private MultipartStream.ProgressNotifier progressNotifier; + + /** + * The boundary, which separates the various parts. + */ + private byte[] multiPartBoundary; + + /** + * The item, which we currently process. + */ + private FileItemStreamImpl currentItem; + + /** + * The current items field name. + */ + private String currentFieldName; + + /** + * Whether we are currently skipping the preamble. + */ + private boolean skipPreamble; + + /** + * Whether the current item may still be read. + */ + private boolean itemValid; + + /** + * Whether we have seen the end of the file. + */ + private boolean eof; + + /** + * Creates a new instance. + * + * @param fileUploadBase Main processor. + * @param requestContext The request context. + * @throws FileUploadException An error occurred while + * parsing the request. + * @throws IOException An I/O error occurred. + */ + public FileItemIteratorImpl(final FileUploadBase fileUploadBase, final RequestContext requestContext) + throws FileUploadException, IOException { + this.fileUploadBase = fileUploadBase; + sizeMax = fileUploadBase.getSizeMax(); + fileSizeMax = fileUploadBase.getFileSizeMax(); + ctx = Objects.requireNonNull(requestContext, "requestContext"); + skipPreamble = true; + findNextItem(); + } + + protected void init(final FileUploadBase fileUploadBase, final RequestContext pRequestContext) + throws FileUploadException, IOException { + final String contentType = ctx.getContentType(); + if ((null == contentType) + || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(FileUploadBase.MULTIPART))) { + throw new InvalidContentTypeException( + format("the request doesn't contain a %s or %s stream, content type header is %s", + FileUploadBase.MULTIPART_FORM_DATA, FileUploadBase.MULTIPART_MIXED, contentType)); + } + final long contentLengthInt = ((UploadContext) ctx).contentLength(); + final long requestSize = UploadContext.class.isAssignableFrom(ctx.getClass()) + // Inline conditional is OK here CHECKSTYLE:OFF + ? ((UploadContext) ctx).contentLength() + : contentLengthInt; + // CHECKSTYLE:ON + + final InputStream input; // N.B. this is eventually closed in MultipartStream processing + if (sizeMax >= 0) { + if (requestSize != -1 && requestSize > sizeMax) { + throw new SizeLimitExceededException( + format("the request was rejected because its size (%s) exceeds the configured maximum (%s)", + requestSize, sizeMax), + requestSize, sizeMax); + } + // N.B. this is eventually closed in MultipartStream processing + input = new LimitedInputStream(ctx.getInputStream(), sizeMax) { + @Override + protected void raiseError(final long pSizeMax, final long pCount) + throws IOException { + final FileUploadException ex = new SizeLimitExceededException( + format("the request was rejected because its size (%s) exceeds the configured maximum (%s)", + pCount, pSizeMax), + pCount, pSizeMax); + throw new FileUploadIOException(ex); + } + }; + } else { + input = ctx.getInputStream(); + } + + String charEncoding = fileUploadBase.getHeaderEncoding(); + if (charEncoding == null) { + charEncoding = ctx.getCharacterEncoding(); + } + + multiPartBoundary = fileUploadBase.getBoundary(contentType); + if (multiPartBoundary == null) { + IOUtils.closeQuietly(input); // avoid possible resource leak + throw new FileUploadException("the request was rejected because no multipart boundary was found"); + } + + progressNotifier = new MultipartStream.ProgressNotifier(fileUploadBase.getProgressListener(), requestSize); + try { + multiPartStream = new MultipartStream(input, multiPartBoundary, progressNotifier); + } catch (final IllegalArgumentException iae) { + IOUtils.closeQuietly(input); // avoid possible resource leak + throw new InvalidContentTypeException( + format("The boundary specified in the %s header is too long", FileUploadBase.CONTENT_TYPE), iae); + } + multiPartStream.setHeaderEncoding(charEncoding); + } + + public MultipartStream getMultiPartStream() throws FileUploadException, IOException { + if (multiPartStream == null) { + init(fileUploadBase, ctx); + } + return multiPartStream; + } + + /** + * Called for finding the next item, if any. + * + * @return True, if an next item was found, otherwise false. + * @throws IOException An I/O error occurred. + */ + private boolean findNextItem() throws FileUploadException, IOException { + if (eof) { + return false; + } + if (currentItem != null) { + currentItem.close(); + currentItem = null; + } + final MultipartStream multi = getMultiPartStream(); + for (; ; ) { + final boolean nextPart; + if (skipPreamble) { + nextPart = multi.skipPreamble(); + } else { + nextPart = multi.readBoundary(); + } + if (!nextPart) { + if (currentFieldName == null) { + // Outer multipart terminated -> No more data + eof = true; + return false; + } + // Inner multipart terminated -> Return to parsing the outer + multi.setBoundary(multiPartBoundary); + currentFieldName = null; + continue; + } + final FileItemHeaders headers = fileUploadBase.getParsedHeaders(multi.readHeaders()); + if (currentFieldName == null) { + // We're parsing the outer multipart + final String fieldName = fileUploadBase.getFieldName(headers); + if (fieldName != null) { + final String subContentType = headers.getHeader(FileUploadBase.CONTENT_TYPE); + if (subContentType != null + && subContentType.toLowerCase(Locale.ENGLISH) + .startsWith(FileUploadBase.MULTIPART_MIXED)) { + currentFieldName = fieldName; + // Multiple files associated with this field name + final byte[] subBoundary = fileUploadBase.getBoundary(subContentType); + multi.setBoundary(subBoundary); + skipPreamble = true; + continue; + } + final String fileName = fileUploadBase.getFileName(headers); + currentItem = new FileItemStreamImpl(this, fileName, + fieldName, headers.getHeader(FileUploadBase.CONTENT_TYPE), + fileName == null, getContentLength(headers)); + currentItem.setHeaders(headers); + progressNotifier.noteItem(); + itemValid = true; + return true; + } + } else { + final String fileName = fileUploadBase.getFileName(headers); + if (fileName != null) { + currentItem = new FileItemStreamImpl(this, fileName, + currentFieldName, + headers.getHeader(FileUploadBase.CONTENT_TYPE), + false, getContentLength(headers)); + currentItem.setHeaders(headers); + progressNotifier.noteItem(); + itemValid = true; + return true; + } + } + multi.discardBodyData(); + } + } + + private long getContentLength(final FileItemHeaders pHeaders) { + try { + return Long.parseLong(pHeaders.getHeader(FileUploadBase.CONTENT_LENGTH)); + } catch (final Exception e) { + return -1; + } + } + + /** + * Returns, whether another instance of {@link FileItemStream} + * is available. + * + * @return True, if one or more additional file items + * are available, otherwise false. + * @throws FileUploadException Parsing or processing the + * file item failed. + * @throws IOException Reading the file item failed. + */ + @Override + public boolean hasNext() throws FileUploadException, IOException { + if (eof) { + return false; + } + if (itemValid) { + return true; + } + try { + return findNextItem(); + } catch (final FileUploadIOException e) { + // unwrap encapsulated SizeException + throw (FileUploadException) e.getCause(); + } + } + + /** + * Returns the next available {@link FileItemStream}. + * + * @return FileItemStream instance, which provides + * access to the next file item. + * @throws NoSuchElementException No more items are + * available. Use {@link #hasNext()} to prevent this exception. + * @throws FileUploadException Parsing or processing the + * file item failed. + * @throws IOException Reading the file item failed. + */ + @Override + public FileItemStream next() throws FileUploadException, IOException { + if (eof || (!itemValid && !hasNext())) { + throw new NoSuchElementException(); + } + itemValid = false; + return currentItem; + } + + @Override + public List getFileItems() throws FileUploadException, IOException { + final List items = new ArrayList<>(); + while (hasNext()) { + final FileItemStream fis = next(); + final FileItem fi = fileUploadBase.getFileItemFactory().createItem(fis.getFieldName(), + fis.getContentType(), fis.isFormField(), fis.getName()); + items.add(fi); + } + return items; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/impl/FileItemStreamImpl.java b/client/src/test/java/org/apache/commons/fileupload2/impl/FileItemStreamImpl.java new file mode 100644 index 0000000000..4884b18f63 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/impl/FileItemStreamImpl.java @@ -0,0 +1,222 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.impl; + +import org.apache.commons.fileupload2.FileItemHeaders; +import org.apache.commons.fileupload2.FileItemStream; +import org.apache.commons.fileupload2.FileUploadException; +import org.apache.commons.fileupload2.InvalidFileNameException; +import org.apache.commons.fileupload2.MultipartStream.ItemInputStream; +import org.apache.commons.fileupload2.pub.FileSizeLimitExceededException; +import org.apache.commons.fileupload2.pub.FileUploadIOException; +import org.apache.commons.fileupload2.util.Closeable; +import org.apache.commons.fileupload2.util.LimitedInputStream; +import org.apache.commons.fileupload2.util.Streams; + +import java.io.IOException; +import java.io.InputStream; + +import static java.lang.String.format; + + +/** + * Default implementation of {@link FileItemStream}. + */ +public class FileItemStreamImpl implements FileItemStream { + /** + * The File Item iterator implementation. + * + * @see FileItemIteratorImpl + */ + private final FileItemIteratorImpl fileItemIteratorImpl; + + /** + * The file items content type. + */ + private final String contentType; + + /** + * The file items field name. + */ + private final String fieldName; + + /** + * The file items file name. + */ + private final String name; + + /** + * Whether the file item is a form field. + */ + private final boolean formField; + + /** + * The file items input stream. + */ + private final InputStream stream; + + /** + * The headers, if any. + */ + private FileItemHeaders headers; + + /** + * Creates a new instance. + * + * @param pFileItemIterator The {@link FileItemIteratorImpl iterator}, which returned this file + * item. + * @param pName The items file name, or null. + * @param pFieldName The items field name. + * @param pContentType The items content type, or null. + * @param pFormField Whether the item is a form field. + * @param pContentLength The items content length, if known, or -1 + * @throws IOException Creating the file item failed. + * @throws FileUploadException Parsing the incoming data stream failed. + */ + public FileItemStreamImpl(final FileItemIteratorImpl pFileItemIterator, final String pName, final String pFieldName, + final String pContentType, final boolean pFormField, + final long pContentLength) throws FileUploadException, IOException { + fileItemIteratorImpl = pFileItemIterator; + name = pName; + fieldName = pFieldName; + contentType = pContentType; + formField = pFormField; + final long fileSizeMax = fileItemIteratorImpl.getFileSizeMax(); + if (fileSizeMax != -1 && pContentLength != -1 + && pContentLength > fileSizeMax) { + final FileSizeLimitExceededException e = + new FileSizeLimitExceededException( + format("The field %s exceeds its maximum permitted size of %s bytes.", + fieldName, fileSizeMax), + pContentLength, fileSizeMax); + e.setFileName(pName); + e.setFieldName(pFieldName); + throw new FileUploadIOException(e); + } + // OK to construct stream now + final ItemInputStream itemStream = fileItemIteratorImpl.getMultiPartStream().newInputStream(); + InputStream istream = itemStream; + if (fileSizeMax != -1) { + istream = new LimitedInputStream(istream, fileSizeMax) { + @Override + protected void raiseError(final long pSizeMax, final long pCount) + throws IOException { + itemStream.close(true); + final FileSizeLimitExceededException e = + new FileSizeLimitExceededException( + format("The field %s exceeds its maximum permitted size of %s bytes.", + fieldName, pSizeMax), + pCount, pSizeMax); + e.setFieldName(fieldName); + e.setFileName(name); + throw new FileUploadIOException(e); + } + }; + } + stream = istream; + } + + /** + * Returns the items content type, or null. + * + * @return Content type, if known, or null. + */ + @Override + public String getContentType() { + return contentType; + } + + /** + * Returns the items field name. + * + * @return Field name. + */ + @Override + public String getFieldName() { + return fieldName; + } + + /** + * Returns the items file name. + * + * @return File name, if known, or null. + * @throws InvalidFileNameException The file name contains a NUL character, + * which might be an indicator of a security attack. If you intend to + * use the file name anyways, catch the exception and use + * InvalidFileNameException#getName(). + */ + @Override + public String getName() { + return Streams.checkFileName(name); + } + + /** + * Returns, whether this is a form field. + * + * @return True, if the item is a form field, + * otherwise false. + */ + @Override + public boolean isFormField() { + return formField; + } + + /** + * Returns an input stream, which may be used to + * read the items contents. + * + * @return Opened input stream. + * @throws IOException An I/O error occurred. + */ + @Override + public InputStream openStream() throws IOException { + if (((Closeable) stream).isClosed()) { + throw new ItemSkippedException(); + } + return stream; + } + + /** + * Closes the file item. + * + * @throws IOException An I/O error occurred. + */ + public void close() throws IOException { + stream.close(); + } + + /** + * Returns the file item headers. + * + * @return The items header object + */ + @Override + public FileItemHeaders getHeaders() { + return headers; + } + + /** + * Sets the file item headers. + * + * @param pHeaders The items header object + */ + @Override + public void setHeaders(final FileItemHeaders pHeaders) { + headers = pHeaders; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/impl/package-info.java b/client/src/test/java/org/apache/commons/fileupload2/impl/package-info.java new file mode 100644 index 0000000000..93c87acb02 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/impl/package-info.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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. + */ + +/** + * Implementations and exceptions utils. + */ +package org.apache.commons.fileupload2.impl; diff --git a/client/src/test/java/org/apache/commons/fileupload2/jaksrvlt/JakSrvltFileCleaner.java b/client/src/test/java/org/apache/commons/fileupload2/jaksrvlt/JakSrvltFileCleaner.java new file mode 100644 index 0000000000..bc23460c5a --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/jaksrvlt/JakSrvltFileCleaner.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.jaksrvlt; + + +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.ServletContextListener; +import org.apache.commons.io.FileCleaningTracker; + +/** + * A servlet context listener, which ensures that the + * {@link FileCleaningTracker}'s reaper thread is terminated, + * when the web application is destroyed. + */ +public class JakSrvltFileCleaner implements ServletContextListener { + + /** + * Attribute name, which is used for storing an instance of + * {@link FileCleaningTracker} in the web application. + */ + public static final String FILE_CLEANING_TRACKER_ATTRIBUTE + = JakSrvltFileCleaner.class.getName() + ".FileCleaningTracker"; + + /** + * Returns the instance of {@link FileCleaningTracker}, which is + * associated with the given {@link ServletContext}. + * + * @param pServletContext The servlet context to query + * @return The contexts tracker + */ + public static FileCleaningTracker + getFileCleaningTracker(final ServletContext pServletContext) { + return (FileCleaningTracker) + pServletContext.getAttribute(FILE_CLEANING_TRACKER_ATTRIBUTE); + } + + /** + * Sets the instance of {@link FileCleaningTracker}, which is + * associated with the given {@link ServletContext}. + * + * @param pServletContext The servlet context to modify + * @param pTracker The tracker to set + */ + public static void setFileCleaningTracker(final ServletContext pServletContext, + final FileCleaningTracker pTracker) { + pServletContext.setAttribute(FILE_CLEANING_TRACKER_ATTRIBUTE, pTracker); + } + + /** + * Called when the web application is initialized. Does + * nothing. + * + * @param sce The servlet context, used for calling + * {@link #setFileCleaningTracker(ServletContext, FileCleaningTracker)}. + */ + @Override + public void contextInitialized(final ServletContextEvent sce) { + setFileCleaningTracker(sce.getServletContext(), + new FileCleaningTracker()); + } + + /** + * Called when the web application is being destroyed. + * Calls {@link FileCleaningTracker#exitWhenFinished()}. + * + * @param sce The servlet context, used for calling + * {@link #getFileCleaningTracker(ServletContext)}. + */ + @Override + public void contextDestroyed(final ServletContextEvent sce) { + getFileCleaningTracker(sce.getServletContext()).exitWhenFinished(); + } +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/jaksrvlt/JakSrvltFileUpload.java b/client/src/test/java/org/apache/commons/fileupload2/jaksrvlt/JakSrvltFileUpload.java new file mode 100644 index 0000000000..dba4d8225c --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/jaksrvlt/JakSrvltFileUpload.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.jaksrvlt; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.fileupload2.FileItem; +import org.apache.commons.fileupload2.FileItemFactory; +import org.apache.commons.fileupload2.FileItemIterator; +import org.apache.commons.fileupload2.FileUpload; +import org.apache.commons.fileupload2.FileUploadBase; +import org.apache.commons.fileupload2.FileUploadException; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * {@code multipart/mixed} encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(HttpServletRequest)} to acquire a list of {@link + * FileItem}s associated with a given HTML + * widget.

+ * + *

How the data for individual parts is stored is determined by the factory + * used to create them; a given part may be in memory, on disk, or somewhere + * else.

+ */ +public class JakSrvltFileUpload extends FileUpload { + + /** + * Constant for HTTP POST method. + */ + private static final String POST_METHOD = "POST"; + + // ---------------------------------------------------------- Class methods + + /** + * Utility method that determines whether the request contains multipart + * content. + * + * @param request The servlet request to be evaluated. Must be non-null. + * @return {@code true} if the request is multipart; + * {@code false} otherwise. + */ + public static final boolean isMultipartContent( + final HttpServletRequest request) { + if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) { + return false; + } + return FileUploadBase.isMultipartContent(new JakSrvltRequestContext(request)); + } + + // ----------------------------------------------------------- Constructors + + /** + * Constructs an uninitialized instance of this class. A factory must be + * configured, using {@code setFileItemFactory()}, before attempting + * to parse requests. + * + * @see FileUpload#FileUpload(FileItemFactory) + */ + public JakSrvltFileUpload() { + } + + /** + * Constructs an instance of this class which uses the supplied factory to + * create {@code FileItem} instances. + * + * @param fileItemFactory The factory to use for creating file items. + * @see FileUpload#FileUpload() + */ + public JakSrvltFileUpload(final FileItemFactory fileItemFactory) { + super(fileItemFactory); + } + + // --------------------------------------------------------- Public methods + + /** + * Processes an RFC 1867 + * compliant {@code multipart/form-data} stream. + * + * @param request The servlet request to be parsed. + * @return A list of {@code FileItem} instances parsed from the + * request, in the order that they were transmitted. + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + */ + public List parseRequest(final HttpServletRequest request) throws FileUploadException { + return parseRequest(new JakSrvltRequestContext(request)); + } + + /** + * Processes an RFC 1867 + * compliant {@code multipart/form-data} stream. + * + * @param request The servlet request to be parsed. + * @return A map of {@code FileItem} instances parsed from the request. + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + * @since 1.3 + */ + public Map> parseParameterMap(final HttpServletRequest request) + throws FileUploadException { + return parseParameterMap(new JakSrvltRequestContext(request)); + } + + /** + * Processes an RFC 1867 + * compliant {@code multipart/form-data} stream. + * + * @param request The servlet request to be parsed. + * @return An iterator to instances of {@code FileItemStream} + * parsed from the request, in the order that they were + * transmitted. + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + * @throws IOException An I/O error occurred. This may be a network + * error while communicating with the client or a problem while + * storing the uploaded content. + */ + public FileItemIterator getItemIterator(final HttpServletRequest request) + throws FileUploadException, IOException { + return super.getItemIterator(new JakSrvltRequestContext(request)); + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/jaksrvlt/JakSrvltRequestContext.java b/client/src/test/java/org/apache/commons/fileupload2/jaksrvlt/JakSrvltRequestContext.java new file mode 100644 index 0000000000..8bbc50e958 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/jaksrvlt/JakSrvltRequestContext.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.jaksrvlt; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.fileupload2.FileUploadBase; +import org.apache.commons.fileupload2.UploadContext; + +import java.io.IOException; +import java.io.InputStream; + +import static java.lang.String.format; + +/** + *

Provides access to the request information needed for a request made to + * an HTTP servlet.

+ * + * @since 1.1 + */ +public class JakSrvltRequestContext implements UploadContext { + + // ----------------------------------------------------- Instance Variables + + /** + * The request for which the context is being provided. + */ + private final HttpServletRequest request; + + // ----------------------------------------------------------- Constructors + + /** + * Construct a context for this request. + * + * @param request The request to which this context applies. + */ + public JakSrvltRequestContext(final HttpServletRequest request) { + this.request = request; + } + + // --------------------------------------------------------- Public Methods + + /** + * Retrieve the character encoding for the request. + * + * @return The character encoding for the request. + */ + @Override + public String getCharacterEncoding() { + return request.getCharacterEncoding(); + } + + /** + * Retrieve the content type of the request. + * + * @return The content type of the request. + */ + @Override + public String getContentType() { + return request.getContentType(); + } + + /** + * Retrieve the content length of the request. + * + * @return The content length of the request. + * @deprecated 1.3 Use {@link #contentLength()} instead + */ + @Override + @Deprecated + public int getContentLength() { + return request.getContentLength(); + } + + /** + * Retrieve the content length of the request. + * + * @return The content length of the request. + * @since 1.3 + */ + @Override + public long contentLength() { + long size; + try { + size = Long.parseLong(request.getHeader(FileUploadBase.CONTENT_LENGTH)); + } catch (final NumberFormatException e) { + size = request.getContentLength(); + } + return size; + } + + /** + * Retrieve the input stream for the request. + * + * @return The input stream for the request. + * @throws IOException if a problem occurs. + */ + @Override + public InputStream getInputStream() throws IOException { + return request.getInputStream(); + } + + /** + * Returns a string representation of this object. + * + * @return a string representation of this object. + */ + @Override + public String toString() { + return format("ContentLength=%s, ContentType=%s", + this.contentLength(), + this.getContentType()); + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/jaksrvlt/package-info.java b/client/src/test/java/org/apache/commons/fileupload2/jaksrvlt/package-info.java new file mode 100644 index 0000000000..796830f623 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/jaksrvlt/package-info.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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. + */ + +/** + *

+ * An implementation of + * {@link org.apache.commons.fileupload2.FileUpload FileUpload} + * for use in servlets conforming to the namespace {@code jakarta.servlet}. + * + *

+ *

+ * The following code fragment demonstrates typical usage. + *

+ *
+ *        DiskFileItemFactory factory = new DiskFileItemFactory();
+ *        // Configure the factory here, if desired.
+ *        JakSrvltFileUpload upload = new JakSrvltFileUpload(factory);
+ *        // Configure the uploader here, if desired.
+ *        List fileItems = upload.parseRequest(request);
+ * 
+ *

+ * Please see the FileUpload + * User Guide + * for further details and examples of how to use this package. + *

+ */ +package org.apache.commons.fileupload2.jaksrvlt; diff --git a/client/src/test/java/org/apache/commons/fileupload2/package-info.java b/client/src/test/java/org/apache/commons/fileupload2/package-info.java new file mode 100644 index 0000000000..e91d991abf --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/package-info.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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. + */ + +/** + *

+ * A component for handling HTML file uploads as specified by + * RFC 1867. + * This component provides support for uploads within both servlets (JSR 53) + * and portlets (JSR 168). + *

+ *

+ * While this package provides the generic functionality for file uploads, + * these classes are not typically used directly. Instead, normal usage + * involves one of the provided extensions of + * {@link org.apache.commons.fileupload2.FileUpload FileUpload} such as + * {@link org.apache.commons.fileupload2.servlet.ServletFileUpload ServletFileUpload} + * or + * {@link org.apache.commons.fileupload2.portlet.PortletFileUpload PortletFileUpload}, + * together with a factory for + * {@link org.apache.commons.fileupload2.FileItem FileItem} instances, + * such as + * {@link org.apache.commons.fileupload2.disk.DiskFileItemFactory DiskFileItemFactory}. + *

+ *

+ * The following is a brief example of typical usage in a servlet, storing + * the uploaded files on disk. + *

+ *
public void doPost(HttpServletRequest req, HttpServletResponse res) {
+ *   DiskFileItemFactory factory = new DiskFileItemFactory();
+ *   // maximum size that will be stored in memory
+ *   factory.setSizeThreshold(4096);
+ *   // the location for saving data that is larger than getSizeThreshold()
+ *   factory.setRepository(new File("/tmp"));
+ *
+ *   ServletFileUpload upload = new ServletFileUpload(factory);
+ *   // maximum size before a FileUploadException will be thrown
+ *   upload.setSizeMax(1000000);
+ *
+ *   List fileItems = upload.parseRequest(req);
+ *   // assume we know there are two files. The first file is a small
+ *   // text file, the second is unknown and is written to a file on
+ *   // the server
+ *   Iterator i = fileItems.iterator();
+ *   String comment = ((FileItem)i.next()).getString();
+ *   FileItem fi = (FileItem)i.next();
+ *   // file name on the client
+ *   String fileName = fi.getName();
+ *   // save comment and file name to database
+ *   ...
+ *   // write the file
+ *   fi.write(new File("/www/uploads/", fileName));
+ * }
+ * 
+ *

+ * In the example above, the first file is loaded into memory as a + * {@code String}. Before calling the {@code getString} method, + * the data may have been in memory or on disk depending on its size. The + * second file we assume it will be large and therefore never explicitly + * load it into memory, though if it is less than 4096 bytes it will be + * in memory before it is written to its final location. When writing to + * the final location, if the data is larger than the threshold, an attempt + * is made to rename the temporary file to the given location. If it cannot + * be renamed, it is streamed to the new location. + *

+ *

+ * Please see the FileUpload + * User Guide + * for further details and examples of how to use this package. + *

+ */ +package org.apache.commons.fileupload2; diff --git a/client/src/test/java/org/apache/commons/fileupload2/portlet/PortletFileUpload.java b/client/src/test/java/org/apache/commons/fileupload2/portlet/PortletFileUpload.java new file mode 100644 index 0000000000..cc59fe92ef --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/portlet/PortletFileUpload.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.portlet; + +import org.apache.commons.fileupload2.FileItem; +import org.apache.commons.fileupload2.FileItemFactory; +import org.apache.commons.fileupload2.FileItemIterator; +import org.apache.commons.fileupload2.FileUpload; +import org.apache.commons.fileupload2.FileUploadBase; +import org.apache.commons.fileupload2.FileUploadException; + +import javax.portlet.ActionRequest; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * {@code multipart/mixed} encoding type, as specified by + * RFC 1867. Use + * {@link org.apache.commons.fileupload2.servlet.ServletFileUpload + * #parseRequest(javax.servlet.http.HttpServletRequest)} to acquire a list + * of {@link FileItem FileItems} associated + * with a given HTML widget.

+ * + *

How the data for individual parts is stored is determined by the factory + * used to create them; a given part may be in memory, on disk, or somewhere + * else.

+ * + * @since 1.1 + */ +public class PortletFileUpload extends FileUpload { + + // ---------------------------------------------------------- Class methods + + /** + * Utility method that determines whether the request contains multipart + * content. + * + * @param request The portlet request to be evaluated. Must be non-null. + * @return {@code true} if the request is multipart; + * {@code false} otherwise. + */ + public static final boolean isMultipartContent(final ActionRequest request) { + return FileUploadBase.isMultipartContent(new PortletRequestContext(request)); + } + + // ----------------------------------------------------------- Constructors + + /** + * Constructs an uninitialized instance of this class. A factory must be + * configured, using {@code setFileItemFactory()}, before attempting + * to parse requests. + * + * @see FileUpload#FileUpload(FileItemFactory) + */ + public PortletFileUpload() { + } + + /** + * Constructs an instance of this class which uses the supplied factory to + * create {@code FileItem} instances. + * + * @param fileItemFactory The factory to use for creating file items. + * @see FileUpload#FileUpload() + */ + public PortletFileUpload(final FileItemFactory fileItemFactory) { + super(fileItemFactory); + } + + // --------------------------------------------------------- Public methods + + /** + * Processes an RFC 1867 + * compliant {@code multipart/form-data} stream. + * + * @param request The portlet request to be parsed. + * @return A list of {@code FileItem} instances parsed from the + * request, in the order that they were transmitted. + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + */ + public List parseRequest(final ActionRequest request) throws FileUploadException { + return parseRequest(new PortletRequestContext(request)); + } + + /** + * Processes an RFC 1867 + * compliant {@code multipart/form-data} stream. + * + * @param request The portlet request to be parsed. + * @return A map of {@code FileItem} instances parsed from the request. + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + * @since 1.3 + */ + public Map> parseParameterMap(final ActionRequest request) throws FileUploadException { + return parseParameterMap(new PortletRequestContext(request)); + } + + /** + * Processes an RFC 1867 + * compliant {@code multipart/form-data} stream. + * + * @param request The portlet request to be parsed. + * @return An iterator to instances of {@code FileItemStream} + * parsed from the request, in the order that they were + * transmitted. + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + * @throws IOException An I/O error occurred. This may be a network + * error while communicating with the client or a problem while + * storing the uploaded content. + */ + public FileItemIterator getItemIterator(final ActionRequest request) throws FileUploadException, IOException { + return super.getItemIterator(new PortletRequestContext(request)); + } +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/portlet/PortletRequestContext.java b/client/src/test/java/org/apache/commons/fileupload2/portlet/PortletRequestContext.java new file mode 100644 index 0000000000..2beba44b0e --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/portlet/PortletRequestContext.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.portlet; + +import org.apache.commons.fileupload2.FileUploadBase; +import org.apache.commons.fileupload2.UploadContext; + +import javax.portlet.ActionRequest; +import java.io.IOException; +import java.io.InputStream; + +import static java.lang.String.format; + +/** + *

Provides access to the request information needed for a request made to + * a portlet.

+ * + * @since 1.1 + */ +public class PortletRequestContext implements UploadContext { + + // ----------------------------------------------------- Instance Variables + + /** + * The request for which the context is being provided. + */ + private final ActionRequest request; + + + // ----------------------------------------------------------- Constructors + + /** + * Construct a context for this request. + * + * @param request The request to which this context applies. + */ + public PortletRequestContext(final ActionRequest request) { + this.request = request; + } + + + // --------------------------------------------------------- Public Methods + + /** + * Retrieve the character encoding for the request. + * + * @return The character encoding for the request. + */ + @Override + public String getCharacterEncoding() { + return request.getCharacterEncoding(); + } + + /** + * Retrieve the content type of the request. + * + * @return The content type of the request. + */ + @Override + public String getContentType() { + return request.getContentType(); + } + + /** + * Retrieve the content length of the request. + * + * @return The content length of the request. + * @deprecated 1.3 Use {@link #contentLength()} instead + */ + @Override + @Deprecated + public int getContentLength() { + return request.getContentLength(); + } + + /** + * Retrieve the content length of the request. + * + * @return The content length of the request. + * @since 1.3 + */ + @Override + public long contentLength() { + long size; + try { + size = Long.parseLong(request.getProperty(FileUploadBase.CONTENT_LENGTH)); + } catch (final NumberFormatException e) { + size = request.getContentLength(); + } + return size; + } + + /** + * Retrieve the input stream for the request. + * + * @return The input stream for the request. + * @throws IOException if a problem occurs. + */ + @Override + public InputStream getInputStream() throws IOException { + return request.getPortletInputStream(); + } + + /** + * Returns a string representation of this object. + * + * @return a string representation of this object. + */ + @Override + public String toString() { + return format("ContentLength=%s, ContentType=%s", + this.contentLength(), + this.getContentType()); + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/portlet/package-info.java b/client/src/test/java/org/apache/commons/fileupload2/portlet/package-info.java new file mode 100644 index 0000000000..d5c2c3bfea --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/portlet/package-info.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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. + */ + +/** + *

+ * An implementation of + * {@link org.apache.commons.fileupload2.FileUpload FileUpload} + * for use in portlets conforming to JSR 168. This implementation requires + * only access to the portlet's current {@code ActionRequest} instance, + * and a suitable + * {@link org.apache.commons.fileupload2.FileItemFactory FileItemFactory} + * implementation, such as + * {@link org.apache.commons.fileupload2.disk.DiskFileItemFactory DiskFileItemFactory}. + *

+ *

+ * The following code fragment demonstrates typical usage. + *

+ *
+ *        DiskFileItemFactory factory = new DiskFileItemFactory();
+ *        // Configure the factory here, if desired.
+ *        PortletFileUpload upload = new PortletFileUpload(factory);
+ *        // Configure the uploader here, if desired.
+ *        List fileItems = upload.parseRequest(request);
+ * 
+ *

+ * Please see the FileUpload + * User Guide + * for further details and examples of how to use this package. + *

+ */ +package org.apache.commons.fileupload2.portlet; diff --git a/client/src/test/java/org/apache/commons/fileupload2/pub/FileSizeLimitExceededException.java b/client/src/test/java/org/apache/commons/fileupload2/pub/FileSizeLimitExceededException.java new file mode 100644 index 0000000000..b87b8dc5e7 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/pub/FileSizeLimitExceededException.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.pub; + +/** + * Thrown to indicate that A files size exceeds the configured maximum. + */ +public class FileSizeLimitExceededException + extends SizeException { + + /** + * The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = 8150776562029630058L; + + /** + * File name of the item, which caused the exception. + */ + private String fileName; + + /** + * Field name of the item, which caused the exception. + */ + private String fieldName; + + /** + * Constructs a {@code SizeExceededException} with + * the specified detail message, and actual and permitted sizes. + * + * @param message The detail message. + * @param actual The actual request size. + * @param permitted The maximum permitted request size. + */ + public FileSizeLimitExceededException(final String message, final long actual, + final long permitted) { + super(message, actual, permitted); + } + + /** + * Returns the file name of the item, which caused the + * exception. + * + * @return File name, if known, or null. + */ + public String getFileName() { + return fileName; + } + + /** + * Sets the file name of the item, which caused the + * exception. + * + * @param pFileName the file name of the item, which caused the exception. + */ + public void setFileName(final String pFileName) { + fileName = pFileName; + } + + /** + * Returns the field name of the item, which caused the + * exception. + * + * @return Field name, if known, or null. + */ + public String getFieldName() { + return fieldName; + } + + /** + * Sets the field name of the item, which caused the + * exception. + * + * @param pFieldName the field name of the item, + * which caused the exception. + */ + public void setFieldName(final String pFieldName) { + fieldName = pFieldName; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/pub/FileUploadIOException.java b/client/src/test/java/org/apache/commons/fileupload2/pub/FileUploadIOException.java new file mode 100644 index 0000000000..e8245e4e50 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/pub/FileUploadIOException.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.pub; + +import org.apache.commons.fileupload2.FileUploadException; + +import java.io.IOException; + +/** + * This exception is thrown for hiding an inner + * {@link FileUploadException} in an {@link IOException}. + */ +public class FileUploadIOException extends IOException { + + /** + * The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = -7047616958165584154L; + + /** + * The exceptions cause; we overwrite the parent + * classes field, which is available since Java + * 1.4 only. + */ + private final FileUploadException cause; + + /** + * Creates a {@code FileUploadIOException} with the + * given cause. + * + * @param pCause The exceptions cause, if any, or null. + */ + public FileUploadIOException(final FileUploadException pCause) { + // We're not doing super(pCause) cause of 1.3 compatibility. + cause = pCause; + } + + /** + * Returns the exceptions cause. + * + * @return The exceptions cause, if any, or null. + */ + @Override + public Throwable getCause() { + return cause; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/pub/IOFileUploadException.java b/client/src/test/java/org/apache/commons/fileupload2/pub/IOFileUploadException.java new file mode 100644 index 0000000000..a35d4d54a7 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/pub/IOFileUploadException.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.pub; + +import org.apache.commons.fileupload2.FileUploadException; + +import java.io.IOException; + +/** + * Thrown to indicate an IOException. + */ +public class IOFileUploadException extends FileUploadException { + + /** + * The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = 1749796615868477269L; + + /** + * The exceptions cause; we overwrite the parent + * classes field, which is available since Java + * 1.4 only. + */ + private final IOException cause; + + /** + * Creates a new instance with the given cause. + * + * @param pMsg The detail message. + * @param pException The exceptions cause. + */ + public IOFileUploadException(final String pMsg, final IOException pException) { + super(pMsg); + cause = pException; + } + + /** + * Returns the exceptions cause. + * + * @return The exceptions cause, if any, or null. + */ + @Override + public Throwable getCause() { + return cause; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/pub/InvalidContentTypeException.java b/client/src/test/java/org/apache/commons/fileupload2/pub/InvalidContentTypeException.java new file mode 100644 index 0000000000..4df548a4b8 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/pub/InvalidContentTypeException.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.pub; + +import org.apache.commons.fileupload2.FileUploadException; + +/** + * Thrown to indicate that the request is not a multipart request. + */ +public class InvalidContentTypeException + extends FileUploadException { + + /** + * The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = -9073026332015646668L; + + /** + * Constructs a {@code InvalidContentTypeException} with no + * detail message. + */ + public InvalidContentTypeException() { + } + + /** + * Constructs an {@code InvalidContentTypeException} with + * the specified detail message. + * + * @param message The detail message. + */ + public InvalidContentTypeException(final String message) { + super(message); + } + + /** + * Constructs an {@code InvalidContentTypeException} with + * the specified detail message and cause. + * + * @param msg The detail message. + * @param cause the original cause + * @since 1.3.1 + */ + public InvalidContentTypeException(final String msg, final Throwable cause) { + super(msg, cause); + } +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/pub/SizeException.java b/client/src/test/java/org/apache/commons/fileupload2/pub/SizeException.java new file mode 100644 index 0000000000..f075b5e336 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/pub/SizeException.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.pub; + +import org.apache.commons.fileupload2.FileUploadException; + +/** + * This exception is thrown, if a requests permitted size + * is exceeded. + */ +abstract class SizeException extends FileUploadException { + + /** + * Serial version UID, being used, if serialized. + */ + private static final long serialVersionUID = -8776225574705254126L; + + /** + * The actual size of the request. + */ + private final long actual; + + /** + * The maximum permitted size of the request. + */ + private final long permitted; + + /** + * Creates a new instance. + * + * @param message The detail message. + * @param actual The actual number of bytes in the request. + * @param permitted The requests size limit, in bytes. + */ + protected SizeException(final String message, final long actual, final long permitted) { + super(message); + this.actual = actual; + this.permitted = permitted; + } + + /** + * Retrieves the actual size of the request. + * + * @return The actual size of the request. + * @since 1.3 + */ + public long getActualSize() { + return actual; + } + + /** + * Retrieves the permitted size of the request. + * + * @return The permitted size of the request. + * @since 1.3 + */ + public long getPermittedSize() { + return permitted; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/pub/SizeLimitExceededException.java b/client/src/test/java/org/apache/commons/fileupload2/pub/SizeLimitExceededException.java new file mode 100644 index 0000000000..2e4056250a --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/pub/SizeLimitExceededException.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.pub; + +/** + * Thrown to indicate that the request size exceeds the configured maximum. + */ +public class SizeLimitExceededException + extends SizeException { + + /** + * The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = -2474893167098052828L; + + /** + * Constructs a {@code SizeExceededException} with + * the specified detail message, and actual and permitted sizes. + * + * @param message The detail message. + * @param actual The actual request size. + * @param permitted The maximum permitted request size. + */ + public SizeLimitExceededException(final String message, final long actual, + final long permitted) { + super(message, actual, permitted); + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/pub/package-info.java b/client/src/test/java/org/apache/commons/fileupload2/pub/package-info.java new file mode 100644 index 0000000000..1b8698b53d --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/pub/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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. + */ + +/** + * Exceptions, and other classes, that are known to be used outside + * of FileUpload. + */ +package org.apache.commons.fileupload2.pub; diff --git a/client/src/test/java/org/apache/commons/fileupload2/servlet/FileCleanerCleanup.java b/client/src/test/java/org/apache/commons/fileupload2/servlet/FileCleanerCleanup.java new file mode 100644 index 0000000000..9a096ce0e0 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/servlet/FileCleanerCleanup.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.servlet; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.ServletContextListener; +import org.apache.commons.io.FileCleaningTracker; + +/** + * A servlet context listener, which ensures that the + * {@link FileCleaningTracker}'s reaper thread is terminated, + * when the web application is destroyed. + */ +public class FileCleanerCleanup implements ServletContextListener { + + /** + * Attribute name, which is used for storing an instance of + * {@link FileCleaningTracker} in the web application. + */ + public static final String FILE_CLEANING_TRACKER_ATTRIBUTE + = FileCleanerCleanup.class.getName() + ".FileCleaningTracker"; + + /** + * Returns the instance of {@link FileCleaningTracker}, which is + * associated with the given {@link ServletContext}. + * + * @param pServletContext The servlet context to query + * @return The contexts tracker + */ + public static FileCleaningTracker + getFileCleaningTracker(final ServletContext pServletContext) { + return (FileCleaningTracker) + pServletContext.getAttribute(FILE_CLEANING_TRACKER_ATTRIBUTE); + } + + /** + * Sets the instance of {@link FileCleaningTracker}, which is + * associated with the given {@link ServletContext}. + * + * @param pServletContext The servlet context to modify + * @param pTracker The tracker to set + */ + public static void setFileCleaningTracker(final ServletContext pServletContext, + final FileCleaningTracker pTracker) { + pServletContext.setAttribute(FILE_CLEANING_TRACKER_ATTRIBUTE, pTracker); + } + + /** + * Called when the web application is initialized. Does + * nothing. + * + * @param sce The servlet context, used for calling + * {@link #setFileCleaningTracker(ServletContext, FileCleaningTracker)}. + */ + @Override + public void contextInitialized(final ServletContextEvent sce) { + setFileCleaningTracker(sce.getServletContext(), + new FileCleaningTracker()); + } + + /** + * Called when the web application is being destroyed. + * Calls {@link FileCleaningTracker#exitWhenFinished()}. + * + * @param sce The servlet context, used for calling + * {@link #getFileCleaningTracker(ServletContext)}. + */ + @Override + public void contextDestroyed(final ServletContextEvent sce) { + getFileCleaningTracker(sce.getServletContext()).exitWhenFinished(); + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/servlet/ServletFileUpload.java b/client/src/test/java/org/apache/commons/fileupload2/servlet/ServletFileUpload.java new file mode 100644 index 0000000000..8b00f2c50a --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/servlet/ServletFileUpload.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.servlet; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.fileupload2.FileItem; +import org.apache.commons.fileupload2.FileItemFactory; +import org.apache.commons.fileupload2.FileItemIterator; +import org.apache.commons.fileupload2.FileUpload; +import org.apache.commons.fileupload2.FileUploadBase; +import org.apache.commons.fileupload2.FileUploadException; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * {@code multipart/mixed} encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(HttpServletRequest)} to acquire a list of {@link + * FileItem}s associated with a given HTML + * widget.

+ * + *

How the data for individual parts is stored is determined by the factory + * used to create them; a given part may be in memory, on disk, or somewhere + * else.

+ */ +public class ServletFileUpload extends FileUpload { + + /** + * Constant for HTTP POST method. + */ + private static final String POST_METHOD = "POST"; + + // ---------------------------------------------------------- Class methods + + /** + * Utility method that determines whether the request contains multipart + * content. + * + * @param request The servlet request to be evaluated. Must be non-null. + * @return {@code true} if the request is multipart; + * {@code false} otherwise. + */ + public static final boolean isMultipartContent( + final HttpServletRequest request) { + if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) { + return false; + } + return FileUploadBase.isMultipartContent(new ServletRequestContext(request)); + } + + // ----------------------------------------------------------- Constructors + + /** + * Constructs an uninitialized instance of this class. A factory must be + * configured, using {@code setFileItemFactory()}, before attempting + * to parse requests. + * + * @see FileUpload#FileUpload(FileItemFactory) + */ + public ServletFileUpload() { + } + + /** + * Constructs an instance of this class which uses the supplied factory to + * create {@code FileItem} instances. + * + * @param fileItemFactory The factory to use for creating file items. + * @see FileUpload#FileUpload() + */ + public ServletFileUpload(final FileItemFactory fileItemFactory) { + super(fileItemFactory); + } + + // --------------------------------------------------------- Public methods + + /** + * Processes an RFC 1867 + * compliant {@code multipart/form-data} stream. + * + * @param request The servlet request to be parsed. + * @return A list of {@code FileItem} instances parsed from the + * request, in the order that they were transmitted. + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + */ + public List parseRequest(final HttpServletRequest request) + throws FileUploadException { + return parseRequest(new ServletRequestContext(request)); + } + + /** + * Processes an RFC 1867 + * compliant {@code multipart/form-data} stream. + * + * @param request The servlet request to be parsed. + * @return A map of {@code FileItem} instances parsed from the request. + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + * @since 1.3 + */ + public Map> parseParameterMap(final HttpServletRequest request) + throws FileUploadException { + return parseParameterMap(new ServletRequestContext(request)); + } + + /** + * Processes an RFC 1867 + * compliant {@code multipart/form-data} stream. + * + * @param request The servlet request to be parsed. + * @return An iterator to instances of {@code FileItemStream} + * parsed from the request, in the order that they were + * transmitted. + * @throws FileUploadException if there are problems reading/parsing + * the request or storing files. + * @throws IOException An I/O error occurred. This may be a network + * error while communicating with the client or a problem while + * storing the uploaded content. + */ + public FileItemIterator getItemIterator(final HttpServletRequest request) + throws FileUploadException, IOException { + return getItemIterator(new ServletRequestContext(request)); + } +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/servlet/ServletRequestContext.java b/client/src/test/java/org/apache/commons/fileupload2/servlet/ServletRequestContext.java new file mode 100644 index 0000000000..246f21f1ae --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/servlet/ServletRequestContext.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.servlet; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.fileupload2.FileUploadBase; +import org.apache.commons.fileupload2.UploadContext; + +import java.io.IOException; +import java.io.InputStream; + +import static java.lang.String.format; + +/** + *

Provides access to the request information needed for a request made to + * an HTTP servlet.

+ * + * @since 1.1 + */ +public class ServletRequestContext implements UploadContext { + + // ----------------------------------------------------- Instance Variables + + /** + * The request for which the context is being provided. + */ + private final HttpServletRequest request; + + // ----------------------------------------------------------- Constructors + + /** + * Construct a context for this request. + * + * @param request The request to which this context applies. + */ + public ServletRequestContext(final HttpServletRequest request) { + this.request = request; + } + + // --------------------------------------------------------- Public Methods + + /** + * Retrieve the character encoding for the request. + * + * @return The character encoding for the request. + */ + @Override + public String getCharacterEncoding() { + return request.getCharacterEncoding(); + } + + /** + * Retrieve the content type of the request. + * + * @return The content type of the request. + */ + @Override + public String getContentType() { + return request.getContentType(); + } + + /** + * Retrieve the content length of the request. + * + * @return The content length of the request. + * @deprecated 1.3 Use {@link #contentLength()} instead + */ + @Override + @Deprecated + public int getContentLength() { + return request.getContentLength(); + } + + /** + * Retrieve the content length of the request. + * + * @return The content length of the request. + * @since 1.3 + */ + @Override + public long contentLength() { + long size; + try { + size = Long.parseLong(request.getHeader(FileUploadBase.CONTENT_LENGTH)); + } catch (final NumberFormatException e) { + size = request.getContentLength(); + } + return size; + } + + /** + * Retrieve the input stream for the request. + * + * @return The input stream for the request. + * @throws IOException if a problem occurs. + */ + @Override + public InputStream getInputStream() throws IOException { + return request.getInputStream(); + } + + /** + * Returns a string representation of this object. + * + * @return a string representation of this object. + */ + @Override + public String toString() { + return format("ContentLength=%s, ContentType=%s", + contentLength(), + getContentType()); + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/servlet/package-info.java b/client/src/test/java/org/apache/commons/fileupload2/servlet/package-info.java new file mode 100644 index 0000000000..06b0cb8704 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/servlet/package-info.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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. + */ + +/** + *

+ * An implementation of + * {@link org.apache.commons.fileupload2.FileUpload FileUpload} + * for use in servlets conforming to JSR 53. This implementation requires + * only access to the servlet's current {@code HttpServletRequest} + * instance, and a suitable + * {@link org.apache.commons.fileupload2.FileItemFactory FileItemFactory} + * implementation, such as + * {@link org.apache.commons.fileupload2.disk.DiskFileItemFactory DiskFileItemFactory}. + *

+ *

+ * The following code fragment demonstrates typical usage. + *

+ *
+ *        DiskFileItemFactory factory = new DiskFileItemFactory();
+ *        // Configure the factory here, if desired.
+ *        ServletFileUpload upload = new ServletFileUpload(factory);
+ *        // Configure the uploader here, if desired.
+ *        List fileItems = upload.parseRequest(request);
+ * 
+ *

+ * Please see the FileUpload + * User Guide + * for further details and examples of how to use this package. + *

+ */ +package org.apache.commons.fileupload2.servlet; diff --git a/client/src/test/java/org/apache/commons/fileupload2/util/Closeable.java b/client/src/test/java/org/apache/commons/fileupload2/util/Closeable.java new file mode 100644 index 0000000000..dce76f8b39 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/util/Closeable.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.util; + +import java.io.IOException; + +/** + * Interface of an object, which may be closed. + */ +public interface Closeable { + + /** + * Closes the object. + * + * @throws IOException An I/O error occurred. + */ + void close() throws IOException; + + /** + * Returns, whether the object is already closed. + * + * @return True, if the object is closed, otherwise false. + * @throws IOException An I/O error occurred. + */ + boolean isClosed() throws IOException; + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/util/FileItemHeadersImpl.java b/client/src/test/java/org/apache/commons/fileupload2/util/FileItemHeadersImpl.java new file mode 100644 index 0000000000..d4413d174b --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/util/FileItemHeadersImpl.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.util; + +import org.apache.commons.fileupload2.FileItemHeaders; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Default implementation of the {@link FileItemHeaders} interface. + * + * @since 1.2.1 + */ +public class FileItemHeadersImpl implements FileItemHeaders, Serializable { + + /** + * Serial version UID, being used, if serialized. + */ + private static final long serialVersionUID = -4455695752627032559L; + + /** + * Map of {@code String} keys to a {@code List} of + * {@code String} instances. + */ + private final Map> headerNameToValueListMap = new LinkedHashMap<>(); + + /** + * {@inheritDoc} + */ + @Override + public String getHeader(final String name) { + final String nameLower = name.toLowerCase(Locale.ENGLISH); + final List headerValueList = headerNameToValueListMap.get(nameLower); + if (null == headerValueList) { + return null; + } + return headerValueList.get(0); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator getHeaderNames() { + return headerNameToValueListMap.keySet().iterator(); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator getHeaders(final String name) { + final String nameLower = name.toLowerCase(Locale.ENGLISH); + List headerValueList = headerNameToValueListMap.get(nameLower); + if (null == headerValueList) { + headerValueList = Collections.emptyList(); + } + return headerValueList.iterator(); + } + + /** + * Method to add header values to this instance. + * + * @param name name of this header + * @param value value of this header + */ + public synchronized void addHeader(final String name, final String value) { + final String nameLower = name.toLowerCase(Locale.ENGLISH); + final List headerValueList = headerNameToValueListMap. + computeIfAbsent(nameLower, k -> new ArrayList<>()); + headerValueList.add(value); + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/util/LimitedInputStream.java b/client/src/test/java/org/apache/commons/fileupload2/util/LimitedInputStream.java new file mode 100644 index 0000000000..ec8c4d8301 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/util/LimitedInputStream.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.util; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * An input stream, which limits its data size. This stream is + * used, if the content length is unknown. + */ +public abstract class LimitedInputStream extends FilterInputStream implements Closeable { + + /** + * The maximum size of an item, in bytes. + */ + private final long sizeMax; + + /** + * The current number of bytes. + */ + private long count; + + /** + * Whether this stream is already closed. + */ + private boolean closed; + + /** + * Creates a new instance. + * + * @param inputStream The input stream, which shall be limited. + * @param pSizeMax The limit; no more than this number of bytes + * shall be returned by the source stream. + */ + public LimitedInputStream(final InputStream inputStream, final long pSizeMax) { + super(inputStream); + sizeMax = pSizeMax; + } + + /** + * Called to indicate, that the input streams limit has + * been exceeded. + * + * @param pSizeMax The input streams limit, in bytes. + * @param pCount The actual number of bytes. + * @throws IOException The called method is expected + * to raise an IOException. + */ + protected abstract void raiseError(long pSizeMax, long pCount) + throws IOException; + + /** + * Called to check, whether the input streams + * limit is reached. + * + * @throws IOException The given limit is exceeded. + */ + private void checkLimit() throws IOException { + if (count > sizeMax) { + raiseError(sizeMax, count); + } + } + + /** + * Reads the next byte of data from this input stream. The value + * byte is returned as an {@code int} in the range + * {@code 0} to {@code 255}. If no byte is available + * because the end of the stream has been reached, the value + * {@code -1} is returned. This method blocks until input data + * is available, the end of the stream is detected, or an exception + * is thrown. + *

+ * This method + * simply performs {@code in.read()} and returns the result. + * + * @return the next byte of data, or {@code -1} if the end of the + * stream is reached. + * @throws IOException if an I/O error occurs. + * @see FilterInputStream#in + */ + @Override + public int read() throws IOException { + final int res = super.read(); + if (res != -1) { + count++; + checkLimit(); + } + return res; + } + + /** + * Reads up to {@code len} bytes of data from this input stream + * into an array of bytes. If {@code len} is not zero, the method + * blocks until some input is available; otherwise, no + * bytes are read and {@code 0} is returned. + *

+ * This method simply performs {@code in.read(b, off, len)} + * and returns the result. + * + * @param b the buffer into which the data is read. + * @param off The start offset in the destination array + * {@code b}. + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or + * {@code -1} if there is no more data because the end of + * the stream has been reached. + * @throws NullPointerException If {@code b} is {@code null}. + * @throws IndexOutOfBoundsException If {@code off} is negative, + * {@code len} is negative, or {@code len} is greater than + * {@code b.length - off} + * @throws IOException if an I/O error occurs. + * @see FilterInputStream#in + */ + @Override + public int read(final byte[] b, final int off, final int len) throws IOException { + final int res = super.read(b, off, len); + if (res > 0) { + count += res; + checkLimit(); + } + return res; + } + + /** + * Returns, whether this stream is already closed. + * + * @return True, if the stream is closed, otherwise false. + * @throws IOException An I/O error occurred. + */ + @Override + public boolean isClosed() throws IOException { + return closed; + } + + /** + * Closes this input stream and releases any system resources + * associated with the stream. + * This + * method simply performs {@code in.close()}. + * + * @throws IOException if an I/O error occurs. + * @see FilterInputStream#in + */ + @Override + public void close() throws IOException { + closed = true; + super.close(); + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/util/Streams.java b/client/src/test/java/org/apache/commons/fileupload2/util/Streams.java new file mode 100644 index 0000000000..07faf497cf --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/util/Streams.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.util; + +import org.apache.commons.fileupload2.InvalidFileNameException; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Utility class for working with streams. + */ +public final class Streams { + + /** + * Private constructor, to prevent instantiation. + * This class has only static methods. + */ + private Streams() { + // Does nothing + } + + /** + * Default buffer size for use in + * {@link #copy(InputStream, OutputStream, boolean)}. + */ + public static final int DEFAULT_BUFFER_SIZE = 8192; + + /** + * Copies the contents of the given {@link InputStream} + * to the given {@link OutputStream}. Shortcut for + *

+     *   copy(pInputStream, pOutputStream, new byte[8192]);
+     * 
+ * + * @param inputStream The input stream, which is being read. + * It is guaranteed, that {@link InputStream#close()} is called + * on the stream. + * @param outputStream The output stream, to which data should + * be written. May be null, in which case the input streams + * contents are simply discarded. + * @param closeOutputStream True guarantees, that + * {@link OutputStream#close()} is called on the stream. + * False indicates, that only + * {@link OutputStream#flush()} should be called finally. + * @return Number of bytes, which have been copied. + * @throws IOException An I/O error occurred. + */ + public static long copy(final InputStream inputStream, final OutputStream outputStream, + final boolean closeOutputStream) + throws IOException { + return copy(inputStream, outputStream, closeOutputStream, new byte[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copies the contents of the given {@link InputStream} + * to the given {@link OutputStream}. + * + * @param inputStream The input stream, which is being read. + * It is guaranteed, that {@link InputStream#close()} is called + * on the stream. + * @param outputStream The output stream, to which data should + * be written. May be null, in which case the input streams + * contents are simply discarded. + * @param closeOutputStream True guarantees, that {@link OutputStream#close()} + * is called on the stream. False indicates, that only + * {@link OutputStream#flush()} should be called finally. + * @param buffer Temporary buffer, which is to be used for + * copying data. + * @return Number of bytes, which have been copied. + * @throws IOException An I/O error occurred. + */ + public static long copy(final InputStream inputStream, + final OutputStream outputStream, final boolean closeOutputStream, + final byte[] buffer) + throws IOException { + try (OutputStream out = outputStream; + InputStream in = inputStream) { + long total = 0; + for (; ; ) { + final int res = in.read(buffer); + if (res == -1) { + break; + } + if (res > 0) { + total += res; + if (out != null) { + out.write(buffer, 0, res); + } + } + } + if (out != null) { + if (closeOutputStream) { + out.close(); + } else { + out.flush(); + } + } + in.close(); + return total; + } + } + + /** + * This convenience method allows to read a + * {@link org.apache.commons.fileupload2.FileItemStream}'s + * content into a string. The platform's default character encoding + * is used for converting bytes into characters. + * + * @param inputStream The input stream to read. + * @return The streams contents, as a string. + * @throws IOException An I/O error occurred. + * @see #asString(InputStream, String) + */ + public static String asString(final InputStream inputStream) throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + copy(inputStream, baos, true); + return baos.toString(); + } + + /** + * This convenience method allows to read a + * {@link org.apache.commons.fileupload2.FileItemStream}'s + * content into a string, using the given character encoding. + * + * @param inputStream The input stream to read. + * @param encoding The character encoding, typically "UTF-8". + * @return The streams contents, as a string. + * @throws IOException An I/O error occurred. + * @see #asString(InputStream) + */ + public static String asString(final InputStream inputStream, final String encoding) + throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + copy(inputStream, baos, true); + return baos.toString(encoding); + } + + /** + * Checks, whether the given file name is valid in the sense, + * that it doesn't contain any NUL characters. If the file name + * is valid, it will be returned without any modifications. Otherwise, + * an {@link InvalidFileNameException} is raised. + * + * @param fileName The file name to check + * @return Unmodified file name, if valid. + * @throws InvalidFileNameException The file name was found to be invalid. + */ + public static String checkFileName(final String fileName) { + if (fileName != null && fileName.indexOf('\u0000') != -1) { + // pFileName.replace("\u0000", "\\0") + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < fileName.length(); i++) { + final char c = fileName.charAt(i); + switch (c) { + case 0: + sb.append("\\0"); + break; + default: + sb.append(c); + break; + } + } + throw new InvalidFileNameException(fileName, + "Invalid file name: " + sb); + } + return fileName; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/util/mime/Base64Decoder.java b/client/src/test/java/org/apache/commons/fileupload2/util/mime/Base64Decoder.java new file mode 100644 index 0000000000..b61c2b921f --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/util/mime/Base64Decoder.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.util.mime; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +/** + * @since 1.3 + */ +final class Base64Decoder { + + /** + * Decoding table value for invalid bytes. + */ + private static final byte INVALID_BYTE = -1; // must be outside range 0-63 + + /** + * Decoding table value for padding bytes, so can detect PAD after conversion. + */ + private static final int PAD_BYTE = -2; // must be outside range 0-63 + + /** + * Mask to treat byte as unsigned integer. + */ + private static final int MASK_BYTE_UNSIGNED = 0xFF; + + /** + * Number of bytes per encoded chunk - 4 6bit bytes produce 3 8bit bytes on output. + */ + private static final int INPUT_BYTES_PER_CHUNK = 4; + + /** + * Set up the encoding table. + */ + private static final byte[] ENCODING_TABLE = { + (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', + (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', + (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', + (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', + (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', + (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', + (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', + (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', + (byte) '7', (byte) '8', (byte) '9', + (byte) '+', (byte) '/' + }; + + /** + * The padding byte. + */ + private static final byte PADDING = (byte) '='; + + /** + * Set up the decoding table; this is indexed by a byte converted to an unsigned int, + * so must be at least as large as the number of different byte values, + * positive and negative and zero. + */ + private static final byte[] DECODING_TABLE = new byte[Byte.MAX_VALUE - Byte.MIN_VALUE + 1]; + + static { + // Initialize as all invalid characters + Arrays.fill(DECODING_TABLE, INVALID_BYTE); + // set up valid characters + for (int i = 0; i < ENCODING_TABLE.length; i++) { + DECODING_TABLE[ENCODING_TABLE[i]] = (byte) i; + } + // Allow pad byte to be easily detected after conversion + DECODING_TABLE[PADDING] = PAD_BYTE; + } + + /** + * Hidden constructor, this class must not be instantiated. + */ + private Base64Decoder() { + // do nothing + } + + /** + * Decode the base 64 encoded byte data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @param data the buffer containing the Base64-encoded data + * @param out the output stream to hold the decoded bytes + * @return the number of bytes produced. + * @throws IOException thrown when the padding is incorrect or the input is truncated. + */ + public static int decode(final byte[] data, final OutputStream out) throws IOException { + int outLen = 0; + final byte[] cache = new byte[INPUT_BYTES_PER_CHUNK]; + int cachedBytes = 0; + + for (final byte b : data) { + final byte d = DECODING_TABLE[MASK_BYTE_UNSIGNED & b]; + if (d == INVALID_BYTE) { + continue; // Ignore invalid bytes + } + cache[cachedBytes++] = d; + if (cachedBytes == INPUT_BYTES_PER_CHUNK) { + // CHECKSTYLE IGNORE MagicNumber FOR NEXT 4 LINES + final byte b1 = cache[0]; + final byte b2 = cache[1]; + final byte b3 = cache[2]; + final byte b4 = cache[3]; + if (b1 == PAD_BYTE || b2 == PAD_BYTE) { + throw new IOException("Invalid Base64 input: incorrect padding, first two bytes cannot be padding"); + } + // Convert 4 6-bit bytes to 3 8-bit bytes + // CHECKSTYLE IGNORE MagicNumber FOR NEXT 1 LINE + out.write((b1 << 2) | (b2 >> 4)); // 6 bits of b1 plus 2 bits of b2 + outLen++; + if (b3 != PAD_BYTE) { + // CHECKSTYLE IGNORE MagicNumber FOR NEXT 1 LINE + out.write((b2 << 4) | (b3 >> 2)); // 4 bits of b2 plus 4 bits of b3 + outLen++; + if (b4 != PAD_BYTE) { + // CHECKSTYLE IGNORE MagicNumber FOR NEXT 1 LINE + out.write((b3 << 6) | b4); // 2 bits of b3 plus 6 bits of b4 + outLen++; + } + } else if (b4 != PAD_BYTE) { // if byte 3 is pad, byte 4 must be pad too + throw new // line wrap to avoid 120 char limit + IOException("Invalid Base64 input: incorrect padding, 4th byte must be padding if 3rd byte is"); + } + cachedBytes = 0; + } + } + // Check for anything left over + if (cachedBytes != 0) { + throw new IOException("Invalid Base64 input: truncated"); + } + return outLen; + } +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/util/mime/MimeUtility.java b/client/src/test/java/org/apache/commons/fileupload2/util/mime/MimeUtility.java new file mode 100644 index 0000000000..847ea415af --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/util/mime/MimeUtility.java @@ -0,0 +1,274 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.util.mime; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Utility class to decode MIME texts. + * + * @since 1.3 + */ +public final class MimeUtility { + + /** + * The marker to indicate text is encoded with BASE64 algorithm. + */ + private static final String BASE64_ENCODING_MARKER = "B"; + + /** + * The marker to indicate text is encoded with QuotedPrintable algorithm. + */ + private static final String QUOTEDPRINTABLE_ENCODING_MARKER = "Q"; + + /** + * If the text contains any encoded tokens, those tokens will be marked with "=?". + */ + private static final String ENCODED_TOKEN_MARKER = "=?"; + + /** + * If the text contains any encoded tokens, those tokens will terminate with "=?". + */ + private static final String ENCODED_TOKEN_FINISHER = "?="; + + /** + * The linear whitespace chars sequence. + */ + private static final String LINEAR_WHITESPACE = " \t\r\n"; + + /** + * Mappings between MIME and Java charset. + */ + private static final Map MIME2JAVA = new HashMap<>(); + + static { + MIME2JAVA.put("iso-2022-cn", "ISO2022CN"); + MIME2JAVA.put("iso-2022-kr", "ISO2022KR"); + MIME2JAVA.put("utf-8", "UTF8"); + MIME2JAVA.put("utf8", "UTF8"); + MIME2JAVA.put("ja_jp.iso2022-7", "ISO2022JP"); + MIME2JAVA.put("ja_jp.eucjp", "EUCJIS"); + MIME2JAVA.put("euc-kr", "KSC5601"); + MIME2JAVA.put("euckr", "KSC5601"); + MIME2JAVA.put("us-ascii", "ISO-8859-1"); + MIME2JAVA.put("x-us-ascii", "ISO-8859-1"); + } + + /** + * Hidden constructor, this class must not be instantiated. + */ + private MimeUtility() { + // do nothing + } + + /** + * Decode a string of text obtained from a mail header into + * its proper form. The text generally will consist of a + * string of tokens, some of which may be encoded using + * base64 encoding. + * + * @param text The text to decode. + * @return The decoded text string. + * @throws UnsupportedEncodingException if the detected encoding in the input text is not supported. + */ + public static String decodeText(final String text) throws UnsupportedEncodingException { + // if the text contains any encoded tokens, those tokens will be marked with "=?". If the + // source string doesn't contain that sequent, no decoding is required. + if (!text.contains(ENCODED_TOKEN_MARKER)) { + return text; + } + + int offset = 0; + final int endOffset = text.length(); + + int startWhiteSpace = -1; + int endWhiteSpace = -1; + + final StringBuilder decodedText = new StringBuilder(text.length()); + + boolean previousTokenEncoded = false; + + while (offset < endOffset) { + char ch = text.charAt(offset); + + // is this a whitespace character? + if (LINEAR_WHITESPACE.indexOf(ch) != -1) { // whitespace found + startWhiteSpace = offset; + while (offset < endOffset) { + // step over the white space characters. + ch = text.charAt(offset); + if (LINEAR_WHITESPACE.indexOf(ch) == -1) { + // record the location of the first non lwsp and drop down to process the + // token characters. + endWhiteSpace = offset; + break; + } + offset++; + } + } else { + // we have a word token. We need to scan over the word and then try to parse it. + final int wordStart = offset; + + while (offset < endOffset) { + // step over the non white space characters. + ch = text.charAt(offset); + if (LINEAR_WHITESPACE.indexOf(ch) != -1) { + break; + } + offset++; + + //NB: Trailing whitespace on these header strings will just be discarded. + } + // pull out the word token. + final String word = text.substring(wordStart, offset); + // is the token encoded? decode the word + if (word.startsWith(ENCODED_TOKEN_MARKER)) { + try { + // if this gives a parsing failure, treat it like a non-encoded word. + final String decodedWord = decodeWord(word); + + // are any whitespace characters significant? Append 'em if we've got 'em. + if (!previousTokenEncoded && startWhiteSpace != -1) { + decodedText.append(text, startWhiteSpace, endWhiteSpace); + startWhiteSpace = -1; + } + // this is definitely a decoded token. + previousTokenEncoded = true; + // and add this to the text. + decodedText.append(decodedWord); + // we continue parsing from here...we allow parsing errors to fall through + // and get handled as normal text. + continue; + + } catch (final ParseException e) { + // just ignore it, skip to next word + } + } + // this is a normal token, so it doesn't matter what the previous token was. Add the white space + // if we have it. + if (startWhiteSpace != -1) { + decodedText.append(text, startWhiteSpace, endWhiteSpace); + startWhiteSpace = -1; + } + // this is not a decoded token. + previousTokenEncoded = false; + decodedText.append(word); + } + } + + return decodedText.toString(); + } + + /** + * Parse a string using the RFC 2047 rules for an "encoded-word" + * type. This encoding has the syntax: + *

+ * encoded-word = "=?" charset "?" encoding "?" encoded-text "?=" + * + * @param word The possibly encoded word value. + * @return The decoded word. + * @throws ParseException in case of a parse error of the RFC 2047 + * @throws UnsupportedEncodingException Thrown when Invalid RFC 2047 encoding was found + */ + private static String decodeWord(final String word) throws ParseException, UnsupportedEncodingException { + // encoded words start with the characters "=?". If this not an encoded word, we throw a + // ParseException for the caller. + + if (!word.startsWith(ENCODED_TOKEN_MARKER)) { + throw new ParseException("Invalid RFC 2047 encoded-word: " + word); + } + + final int charsetPos = word.indexOf('?', 2); + if (charsetPos == -1) { + throw new ParseException("Missing charset in RFC 2047 encoded-word: " + word); + } + + // pull out the character set information (this is the MIME name at this point). + final String charset = word.substring(2, charsetPos).toLowerCase(Locale.ENGLISH); + + // now pull out the encoding token the same way. + final int encodingPos = word.indexOf('?', charsetPos + 1); + if (encodingPos == -1) { + throw new ParseException("Missing encoding in RFC 2047 encoded-word: " + word); + } + + final String encoding = word.substring(charsetPos + 1, encodingPos); + + // and finally the encoded text. + final int encodedTextPos = word.indexOf(ENCODED_TOKEN_FINISHER, encodingPos + 1); + if (encodedTextPos == -1) { + throw new ParseException("Missing encoded text in RFC 2047 encoded-word: " + word); + } + + final String encodedText = word.substring(encodingPos + 1, encodedTextPos); + + // seems a bit silly to encode a null string, but easy to deal with. + if (encodedText.isEmpty()) { + return ""; + } + + try { + // the decoder writes directly to an output stream. + final ByteArrayOutputStream out = new ByteArrayOutputStream(encodedText.length()); + + final byte[] encodedData = encodedText.getBytes(StandardCharsets.US_ASCII); + + // Base64 encoded? + if (encoding.equals(BASE64_ENCODING_MARKER)) { + Base64Decoder.decode(encodedData, out); + } else if (encoding.equals(QUOTEDPRINTABLE_ENCODING_MARKER)) { // maybe quoted printable. + QuotedPrintableDecoder.decode(encodedData, out); + } else { + throw new UnsupportedEncodingException("Unknown RFC 2047 encoding: " + encoding); + } + // get the decoded byte data and convert into a string. + final byte[] decodedData = out.toByteArray(); + return new String(decodedData, javaCharset(charset)); + } catch (final IOException e) { + throw new UnsupportedEncodingException("Invalid RFC 2047 encoding"); + } + } + + /** + * Translate a MIME standard character set name into the Java + * equivalent. + * + * @param charset The MIME standard name. + * @return The Java equivalent for this name. + */ + private static String javaCharset(final String charset) { + // nothing in, nothing out. + if (charset == null) { + return null; + } + + final String mappedCharset = MIME2JAVA.get(charset.toLowerCase(Locale.ENGLISH)); + // if there is no mapping, then the original name is used. Many of the MIME character set + // names map directly back into Java. The reverse isn't necessarily true. + if (mappedCharset == null) { + return charset; + } + return mappedCharset; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/util/mime/ParseException.java b/client/src/test/java/org/apache/commons/fileupload2/util/mime/ParseException.java new file mode 100644 index 0000000000..7981ea4907 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/util/mime/ParseException.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.util.mime; + +/** + * @since 1.3 + */ +final class ParseException extends Exception { + + /** + * The UID to use when serializing this instance. + */ + private static final long serialVersionUID = 5355281266579392077L; + + /** + * Constructs a new exception with the specified detail message. + * + * @param message the detail message. + */ + ParseException(final String message) { + super(message); + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/util/mime/QuotedPrintableDecoder.java b/client/src/test/java/org/apache/commons/fileupload2/util/mime/QuotedPrintableDecoder.java new file mode 100644 index 0000000000..88c0270b46 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/util/mime/QuotedPrintableDecoder.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.util.mime; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * @since 1.3 + */ +final class QuotedPrintableDecoder { + + /** + * The shift value required to create the upper nibble + * from the first of 2 byte values converted from ascii hex. + */ + private static final int UPPER_NIBBLE_SHIFT = Byte.SIZE / 2; + + /** + * Hidden constructor, this class must not be instantiated. + */ + private QuotedPrintableDecoder() { + // do nothing + } + + /** + * Decode the encoded byte data writing it to the given output stream. + * + * @param data The array of byte data to decode. + * @param out The output stream used to return the decoded data. + * @return the number of bytes produced. + * @throws IOException if an IO error occurs + */ + public static int decode(final byte[] data, final OutputStream out) throws IOException { + int off = 0; + final int length = data.length; + final int endOffset = off + length; + int bytesWritten = 0; + + while (off < endOffset) { + final byte ch = data[off++]; + + // space characters were translated to '_' on encode, so we need to translate them back. + if (ch == '_') { + out.write(' '); + } else if (ch == '=') { + // we found an encoded character. Reduce the 3 char sequence to one. + // but first, make sure we have two characters to work with. + if (off + 1 >= endOffset) { + throw new IOException("Invalid quoted printable encoding; truncated escape sequence"); + } + + final byte b1 = data[off++]; + final byte b2 = data[off++]; + + // we've found an encoded carriage return. The next char needs to be a newline + if (b1 == '\r') { + if (b2 != '\n') { + throw new IOException("Invalid quoted printable encoding; CR must be followed by LF"); + } + // this was a soft linebreak inserted by the encoding. We just toss this away + // on decode. + } else { + // this is a hex pair we need to convert back to a single byte. + final int c1 = hexToBinary(b1); + final int c2 = hexToBinary(b2); + out.write((c1 << UPPER_NIBBLE_SHIFT) | c2); + // 3 bytes in, one byte out + bytesWritten++; + } + } else { + // simple character, just write it out. + out.write(ch); + bytesWritten++; + } + } + + return bytesWritten; + } + + /** + * Convert a hex digit to the binary value it represents. + * + * @param b the ascii hex byte to convert (0-0, A-F, a-f) + * @return the int value of the hex byte, 0-15 + * @throws IOException if the byte is not a valid hex digit. + */ + private static int hexToBinary(final byte b) throws IOException { + // CHECKSTYLE IGNORE MagicNumber FOR NEXT 1 LINE + final int i = Character.digit((char) b, 16); + if (i == -1) { + throw new IOException("Invalid quoted printable encoding: not a valid hex digit: " + b); + } + return i; + } + +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/util/mime/RFC2231Utility.java b/client/src/test/java/org/apache/commons/fileupload2/util/mime/RFC2231Utility.java new file mode 100644 index 0000000000..4033765197 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/util/mime/RFC2231Utility.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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.apache.commons.fileupload2.util.mime; + +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; + +/** + * Utility class to decode/encode character set on HTTP Header fields based on RFC 2231. + * This implementation adheres to RFC 5987 in particular, which was defined for HTTP headers + *

+ * RFC 5987 builds on RFC 2231, but has lesser scope like + * mandatory charset definition + * and no parameter continuation + * + *

+ * + * @see RFC 2231 + * @see RFC 5987 + */ +public final class RFC2231Utility { + /** + * The Hexadecimal values char array. + */ + private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray(); + /** + * The Hexadecimal representation of 127. + */ + private static final byte MASK = 0x7f; + /** + * The Hexadecimal representation of 128. + */ + private static final int MASK_128 = 0x80; + /** + * The Hexadecimal decode value. + */ + private static final byte[] HEX_DECODE = new byte[MASK_128]; + + // create a ASCII decoded array of Hexadecimal values + static { + for (int i = 0; i < HEX_DIGITS.length; i++) { + HEX_DECODE[HEX_DIGITS[i]] = (byte) i; + HEX_DECODE[Character.toLowerCase(HEX_DIGITS[i])] = (byte) i; + } + } + + /** + * Private constructor so that no instances can be created. This class + * contains only static utility methods. + */ + private RFC2231Utility() { + } + + /** + * Checks if Asterisk (*) at the end of parameter name to indicate, + * if it has charset and language information to decode the value. + * + * @param paramName The parameter, which is being checked. + * @return {@code true}, if encoded as per RFC 2231, {@code false} otherwise + */ + public static boolean hasEncodedValue(final String paramName) { + if (paramName != null) { + return paramName.lastIndexOf('*') == (paramName.length() - 1); + } + return false; + } + + /** + * If {@code paramName} has Asterisk (*) at the end, it will be stripped off, + * else the passed value will be returned. + * + * @param paramName The parameter, which is being inspected. + * @return stripped {@code paramName} of Asterisk (*), if RFC2231 encoded + */ + public static String stripDelimiter(final String paramName) { + if (hasEncodedValue(paramName)) { + final StringBuilder paramBuilder = new StringBuilder(paramName); + paramBuilder.deleteCharAt(paramName.lastIndexOf('*')); + return paramBuilder.toString(); + } + return paramName; + } + + /** + * Decode a string of text obtained from a HTTP header as per RFC 2231 + * + * Eg 1. {@code us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A} + * will be decoded to {@code This is ***fun***} + * + * Eg 2. {@code iso-8859-1'en'%A3%20rate} + * will be decoded to {@code £ rate}. + * + * Eg 3. {@code UTF-8''%c2%a3%20and%20%e2%82%ac%20rates} + * will be decoded to {@code £ and € rates}. + * + * @param encodedText - Text to be decoded has a format of {@code ''} + * and ASCII only + * @return Decoded text based on charset encoding + * @throws UnsupportedEncodingException The requested character set wasn't found. + */ + public static String decodeText(final String encodedText) throws UnsupportedEncodingException { + final int langDelimitStart = encodedText.indexOf('\''); + if (langDelimitStart == -1) { + // missing charset + return encodedText; + } + final String mimeCharset = encodedText.substring(0, langDelimitStart); + final int langDelimitEnd = encodedText.indexOf('\'', langDelimitStart + 1); + if (langDelimitEnd == -1) { + // missing language + return encodedText; + } + final byte[] bytes = fromHex(encodedText.substring(langDelimitEnd + 1)); + return new String(bytes, getJavaCharset(mimeCharset)); + } + + /** + * Convert {@code text} to their corresponding Hex value. + * + * @param text - ASCII text input + * @return Byte array of characters decoded from ASCII table + */ + private static byte[] fromHex(final String text) { + final int shift = 4; + final ByteArrayOutputStream out = new ByteArrayOutputStream(text.length()); + for (int i = 0; i < text.length(); ) { + final char c = text.charAt(i++); + if (c == '%') { + if (i > text.length() - 2) { + break; // unterminated sequence + } + final byte b1 = HEX_DECODE[text.charAt(i++) & MASK]; + final byte b2 = HEX_DECODE[text.charAt(i++) & MASK]; + out.write((b1 << shift) | b2); + } else { + out.write((byte) c); + } + } + return out.toByteArray(); + } + + private static String getJavaCharset(final String mimeCharset) { + // good enough for standard values + return mimeCharset; + } +} diff --git a/client/src/test/java/org/apache/commons/fileupload2/util/mime/package-info.java b/client/src/test/java/org/apache/commons/fileupload2/util/mime/package-info.java new file mode 100644 index 0000000000..6b9c410d33 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/util/mime/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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. + */ + +/** + * MIME decoder implementation, imported and retailed from + * Apache Geronimo. + */ +package org.apache.commons.fileupload2.util.mime; diff --git a/client/src/test/java/org/apache/commons/fileupload2/util/package-info.java b/client/src/test/java/org/apache/commons/fileupload2/util/package-info.java new file mode 100644 index 0000000000..95817a14a7 --- /dev/null +++ b/client/src/test/java/org/apache/commons/fileupload2/util/package-info.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 + * + * 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. + */ + +/** + * This package contains various IO related utility classes + * or methods, which are basically reusable and not necessarily + * restricted to the scope of a file upload. + */ +package org.apache.commons.fileupload2.util; diff --git a/client/src/test/java/org/asynchttpclient/AbstractBasicTest.java b/client/src/test/java/org/asynchttpclient/AbstractBasicTest.java new file mode 100644 index 0000000000..2dcfa859dc --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/AbstractBasicTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 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.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; + + protected Server server; + protected int port1 = -1; + protected int port2 = -1; + + @BeforeAll + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector1 = addHttpConnector(server); + server.setHandler(configureHandler()); + ServerConnector connector2 = addHttpConnector(server); + server.start(); + + port1 = connector1.getLocalPort(); + port2 = connector2.getLocalPort(); + + logger.info("Local HTTP server started successfully"); + } + + @AfterAll + public void tearDownGlobal() throws Exception { + logger.debug("Shutting down local server: {}", server); + + if (server != null) { + server.stop(); + } + } + + protected String getTargetUrl() { + return String.format("http://localhost:%d/foo/test", port1); + } + + protected String getTargetUrl2() { + return String.format("https://localhost:%d/foo/test", port2); + } + + public AbstractHandler configureHandler() throws Exception { + return new EchoHandler(); + } + + public static class AsyncCompletionHandlerAdapter extends AsyncCompletionHandler { + + private static final Logger logger = LoggerFactory.getLogger(AsyncCompletionHandlerAdapter.class); + + @Override + public Response onCompleted(Response response) throws Exception { + return response; + } + + @Override + public void onThrowable(Throwable t) { + logger.error(t.getMessage(), t); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/AsyncHttpClientDefaultsTest.java b/client/src/test/java/org/asynchttpclient/AsyncHttpClientDefaultsTest.java new file mode 100644 index 0000000000..d125a9fa48 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/AsyncHttpClientDefaultsTest.java @@ -0,0 +1,226 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.config.AsyncHttpClientConfigDefaults; +import org.asynchttpclient.config.AsyncHttpClientConfigHelper; + +import java.lang.reflect.Method; +import java.time.Duration; + +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.ASYNC_CLIENT_CONFIG_ROOT; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class AsyncHttpClientDefaultsTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultUseOnlyEpollNativeTransport() { + assertFalse(AsyncHttpClientConfigDefaults.defaultUseOnlyEpollNativeTransport()); + testBooleanSystemProperty("useOnlyEpollNativeTransport", "defaultUseOnlyEpollNativeTransport", "false"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultMaxTotalConnections() { + assertEquals(AsyncHttpClientConfigDefaults.defaultMaxConnections(), -1); + testIntegerSystemProperty("maxConnections", "defaultMaxConnections", "100"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultMaxConnectionPerHost() { + assertEquals(AsyncHttpClientConfigDefaults.defaultMaxConnectionsPerHost(), -1); + testIntegerSystemProperty("maxConnectionsPerHost", "defaultMaxConnectionsPerHost", "100"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultConnectTimeOut() { + assertEquals(AsyncHttpClientConfigDefaults.defaultConnectTimeout(), Duration.ofSeconds(5)); + testDurationSystemProperty("connectTimeout", "defaultConnectTimeout", "PT0.1S"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultPooledConnectionIdleTimeout() { + assertEquals(AsyncHttpClientConfigDefaults.defaultPooledConnectionIdleTimeout(), Duration.ofMinutes(1)); + testDurationSystemProperty("pooledConnectionIdleTimeout", "defaultPooledConnectionIdleTimeout", "PT0.1S"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultReadTimeout() { + assertEquals(AsyncHttpClientConfigDefaults.defaultReadTimeout(), Duration.ofSeconds(60)); + testDurationSystemProperty("readTimeout", "defaultReadTimeout", "PT0.1S"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultRequestTimeout() { + assertEquals(AsyncHttpClientConfigDefaults.defaultRequestTimeout(), Duration.ofSeconds(60)); + testDurationSystemProperty("requestTimeout", "defaultRequestTimeout", "PT0.1S"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultConnectionTtl() { + assertEquals(AsyncHttpClientConfigDefaults.defaultConnectionTtl(), Duration.ofMillis(-1)); + testDurationSystemProperty("connectionTtl", "defaultConnectionTtl", "PT0.1S"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultFollowRedirect() { + assertFalse(AsyncHttpClientConfigDefaults.defaultFollowRedirect()); + testBooleanSystemProperty("followRedirect", "defaultFollowRedirect", "true"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultMaxRedirects() { + assertEquals(AsyncHttpClientConfigDefaults.defaultMaxRedirects(), 5); + testIntegerSystemProperty("maxRedirects", "defaultMaxRedirects", "100"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultCompressionEnforced() { + assertFalse(AsyncHttpClientConfigDefaults.defaultCompressionEnforced()); + testBooleanSystemProperty("compressionEnforced", "defaultCompressionEnforced", "true"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultUserAgent() { + assertEquals(AsyncHttpClientConfigDefaults.defaultUserAgent(), "AHC/2.1"); + testStringSystemProperty("userAgent", "defaultUserAgent", "MyAHC"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultUseProxySelector() { + assertFalse(AsyncHttpClientConfigDefaults.defaultUseProxySelector()); + testBooleanSystemProperty("useProxySelector", "defaultUseProxySelector", "true"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultUseProxyProperties() { + assertFalse(AsyncHttpClientConfigDefaults.defaultUseProxyProperties()); + testBooleanSystemProperty("useProxyProperties", "defaultUseProxyProperties", "true"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultStrict302Handling() { + assertFalse(AsyncHttpClientConfigDefaults.defaultStrict302Handling()); + testBooleanSystemProperty("strict302Handling", "defaultStrict302Handling", "true"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultAllowPoolingConnection() { + assertTrue(AsyncHttpClientConfigDefaults.defaultKeepAlive()); + testBooleanSystemProperty("keepAlive", "defaultKeepAlive", "false"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultMaxRequestRetry() { + assertEquals(AsyncHttpClientConfigDefaults.defaultMaxRequestRetry(), 5); + testIntegerSystemProperty("maxRequestRetry", "defaultMaxRequestRetry", "100"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultDisableUrlEncodingForBoundRequests() { + assertFalse(AsyncHttpClientConfigDefaults.defaultDisableUrlEncodingForBoundRequests()); + testBooleanSystemProperty("disableUrlEncodingForBoundRequests", "defaultDisableUrlEncodingForBoundRequests", "true"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultUseInsecureTrustManager() { + assertFalse(AsyncHttpClientConfigDefaults.defaultUseInsecureTrustManager()); + testBooleanSystemProperty("useInsecureTrustManager", "defaultUseInsecureTrustManager", "false"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultHashedWheelTimerTickDuration() { + assertEquals(AsyncHttpClientConfigDefaults.defaultHashedWheelTimerTickDuration(), 100); + testIntegerSystemProperty("hashedWheelTimerTickDuration", "defaultHashedWheelTimerTickDuration", "100"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultHashedWheelTimerSize() { + assertEquals(AsyncHttpClientConfigDefaults.defaultHashedWheelTimerSize(), 512); + testIntegerSystemProperty("hashedWheelTimerSize", "defaultHashedWheelTimerSize", "512"); + } + + private void testIntegerSystemProperty(String propertyName, String methodName, String value) { + String previous = System.getProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName); + System.setProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName, value); + AsyncHttpClientConfigHelper.reloadProperties(); + try { + Method method = AsyncHttpClientConfigDefaults.class.getMethod(methodName); + assertEquals(method.invoke(null), Integer.parseInt(value)); + } catch (Exception e) { + fail("Couldn't find or execute method : " + methodName, e); + } + if (previous != null) { + System.setProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName, previous); + } else { + System.clearProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName); + } + } + + private static void testBooleanSystemProperty(String propertyName, String methodName, String value) { + String previous = System.getProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName); + System.setProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName, value); + AsyncHttpClientConfigHelper.reloadProperties(); + try { + Method method = AsyncHttpClientConfigDefaults.class.getMethod(methodName); + assertEquals(method.invoke(null), Boolean.parseBoolean(value)); + } catch (Exception e) { + fail("Couldn't find or execute method : " + methodName, e); + } + if (previous != null) { + System.setProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName, previous); + } else { + System.clearProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName); + } + } + + private static void testStringSystemProperty(String propertyName, String methodName, String value) { + String previous = System.getProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName); + System.setProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName, value); + AsyncHttpClientConfigHelper.reloadProperties(); + try { + Method method = AsyncHttpClientConfigDefaults.class.getMethod(methodName); + assertEquals(method.invoke(null), value); + } catch (Exception e) { + fail("Couldn't find or execute method : " + methodName, e); + } + if (previous != null) { + System.setProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName, previous); + } else { + System.clearProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName); + } + } + + private static void testDurationSystemProperty(String propertyName, String methodName, String value) { + String previous = System.getProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName); + System.setProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName, value); + AsyncHttpClientConfigHelper.reloadProperties(); + try { + Method method = AsyncHttpClientConfigDefaults.class.getMethod(methodName); + assertEquals(method.invoke(null), Duration.parse(value)); + } catch (Exception e) { + fail("Couldn't find or execute method : " + methodName, e); + } + if (previous != null) { + System.setProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName, previous); + } else { + System.clearProperty(ASYNC_CLIENT_CONFIG_ROOT + propertyName); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java b/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java new file mode 100644 index 0000000000..90a515fca4 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java @@ -0,0 +1,520 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpHeaders; +import org.asynchttpclient.testserver.HttpServer; +import org.asynchttpclient.testserver.HttpTest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Timeout; + +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static io.netty.handler.codec.http.HttpHeaderNames.ALLOW; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.netty.handler.codec.http.HttpHeaderNames.LOCATION; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.AsyncHandlerAdapter; +import static org.asynchttpclient.test.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; +import static org.asynchttpclient.test.TestUtils.TIMEOUT; +import static org.asynchttpclient.test.TestUtils.assertContentTypesEquals; +import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace; +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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class AsyncStreamHandlerTest extends HttpTest { + + private static final String RESPONSE = "param_1=value_1"; + + private HttpServer server; + + @BeforeEach + public void start() throws Throwable { + server = new HttpServer(); + server.start(); + } + + @AfterEach + public void stop() throws Throwable { + server.close(); + } + + private String getTargetUrl() { + return server.getHttpUrl() + "/foo/bar"; + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void getWithOnHeadersReceivedAbort() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + client.prepareGet(getTargetUrl()).execute(new AsyncHandlerAdapter() { + + @Override + public State onHeadersReceived(HttpHeaders headers) { + assertContentTypesEquals(headers.get(CONTENT_TYPE), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + return State.ABORT; + } + }).get(5, TimeUnit.SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void asyncStreamPOSTTest() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + + server.enqueueEcho(); + + String responseBody = client.preparePost(getTargetUrl()) + .setHeader(CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED) + .addFormParam("param_1", "value_1") + .execute(new AsyncHandlerAdapter() { + private final StringBuilder builder = new StringBuilder(); + + @Override + public State onHeadersReceived(HttpHeaders headers) { + assertContentTypesEquals(headers.get(CONTENT_TYPE), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + for (Map.Entry header : headers) { + if (header.getKey().startsWith("X-param")) { + builder.append(header.getKey().substring(2)).append('=').append(header.getValue()).append('&'); + } + } + return State.CONTINUE; + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart content) { + return State.CONTINUE; + } + + @Override + public String onCompleted() { + if (builder.length() > 0) { + builder.setLength(builder.length() - 1); + } + return builder.toString(); + } + }).get(10, TimeUnit.SECONDS); + + assertEquals(responseBody, RESPONSE); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void asyncStreamInterruptTest() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + + server.enqueueEcho(); + + final AtomicBoolean onHeadersReceived = new AtomicBoolean(); + final AtomicBoolean onBodyPartReceived = new AtomicBoolean(); + final AtomicBoolean onThrowable = new AtomicBoolean(); + + client.preparePost(getTargetUrl()) + .setHeader(CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED) + .addFormParam("param_1", "value_1") + .execute(new AsyncHandlerAdapter() { + + @Override + public State onHeadersReceived(HttpHeaders headers) { + onHeadersReceived.set(true); + assertContentTypesEquals(headers.get(CONTENT_TYPE), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + return State.ABORT; + } + + @Override + public State onBodyPartReceived(final HttpResponseBodyPart content) { + onBodyPartReceived.set(true); + return State.ABORT; + } + + @Override + public void onThrowable(Throwable t) { + onThrowable.set(true); + } + }).get(5, TimeUnit.SECONDS); + + assertTrue(onHeadersReceived.get(), "Headers weren't received"); + assertFalse(onBodyPartReceived.get(), "Abort not working"); + assertFalse(onThrowable.get(), "Shouldn't get an exception"); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void asyncStreamFutureTest() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + + server.enqueueEcho(); + + final AtomicBoolean onHeadersReceived = new AtomicBoolean(); + final AtomicBoolean onThrowable = new AtomicBoolean(); + + String responseBody = client.preparePost(getTargetUrl()) + .addFormParam("param_1", "value_1") + .execute(new AsyncHandlerAdapter() { + private final StringBuilder builder = new StringBuilder(); + + @Override + public State onHeadersReceived(HttpHeaders headers) { + assertContentTypesEquals(headers.get(CONTENT_TYPE), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + onHeadersReceived.set(true); + for (Map.Entry header : headers) { + if (header.getKey().startsWith("X-param")) { + builder.append(header.getKey().substring(2)).append('=').append(header.getValue()).append('&'); + } + } + return State.CONTINUE; + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart content) { + return State.CONTINUE; + } + + @Override + public String onCompleted() { + if (builder.length() > 0) { + builder.setLength(builder.length() - 1); + } + return builder.toString().trim(); + } + + @Override + public void onThrowable(Throwable t) { + onThrowable.set(true); + } + }).get(5, TimeUnit.SECONDS); + + assertTrue(onHeadersReceived.get(), "Headers weren't received"); + assertFalse(onThrowable.get(), "Shouldn't get an exception"); + assertEquals(responseBody, RESPONSE, "Unexpected response body"); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void asyncStreamThrowableRefusedTest() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + + server.enqueueEcho(); + + final CountDownLatch l = new CountDownLatch(1); + client.prepareGet(getTargetUrl()).execute(new AsyncHandlerAdapter() { + + @Override + public State onHeadersReceived(HttpHeaders headers) { + throw unknownStackTrace(new RuntimeException("FOO"), AsyncStreamHandlerTest.class, "asyncStreamThrowableRefusedTest"); + } + + @Override + public void onThrowable(Throwable t) { + try { + if (t.getMessage() != null) { + assertEquals(t.getMessage(), "FOO"); + } + } finally { + l.countDown(); + } + } + }); + + if (!l.await(10, TimeUnit.SECONDS)) { + fail("Timed out"); + } + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void asyncStreamReusePOSTTest() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + + server.enqueueEcho(); + + final AtomicReference responseHeaders = new AtomicReference<>(); + + BoundRequestBuilder rb = client.preparePost(getTargetUrl()) + .setHeader(CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED) + .addFormParam("param_1", "value_1"); + + Future f = rb.execute(new AsyncHandlerAdapter() { + private final StringBuilder builder = new StringBuilder(); + + @Override + public State onHeadersReceived(HttpHeaders headers) { + responseHeaders.set(headers); + for (Map.Entry header : headers) { + if (header.getKey().startsWith("X-param")) { + builder.append(header.getKey().substring(2)).append('=').append(header.getValue()).append('&'); + } + } + return State.CONTINUE; + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart content) { + return State.CONTINUE; + } + + @Override + public String onCompleted() { + if (builder.length() > 0) { + builder.setLength(builder.length() - 1); + } + return builder.toString(); + } + }); + + String r = f.get(5, TimeUnit.SECONDS); + HttpHeaders h = responseHeaders.get(); + assertNotNull(h, "Should receive non null headers"); + assertContentTypesEquals(h.get(CONTENT_TYPE), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + assertNotNull(r, "No response body"); + assertEquals(r.trim(), RESPONSE, "Unexpected response body"); + + responseHeaders.set(null); + + server.enqueueEcho(); + + // Let do the same again + f = rb.execute(new AsyncHandlerAdapter() { + private final StringBuilder builder = new StringBuilder(); + + @Override + public State onHeadersReceived(HttpHeaders headers) { + responseHeaders.set(headers); + return State.CONTINUE; + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart content) { + builder.append(new String(content.getBodyPartBytes())); + return State.CONTINUE; + } + + @Override + public String onCompleted() { + return builder.toString(); + } + }); + + f.get(5, TimeUnit.SECONDS); + h = responseHeaders.get(); + assertNotNull(h, "Should receive non null headers"); + assertContentTypesEquals(h.get(CONTENT_TYPE), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + assertNotNull(r, "No response body"); + assertEquals(r.trim(), RESPONSE, "Unexpected response body"); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void asyncStream302RedirectWithBody() throws Throwable { + withClient(config().setFollowRedirect(true)).run(client -> + withServer(server).run(server -> { + + String originalUrl = server.getHttpUrl() + "/original"; + String redirectUrl = server.getHttpUrl() + "/redirect"; + + server.enqueueResponse(response -> { + response.setStatus(302); + response.setHeader(LOCATION.toString(), redirectUrl); + response.getOutputStream().println("You are being asked to redirect to " + redirectUrl); + }); + server.enqueueOk(); + + Response response = client.prepareGet(originalUrl).execute().get(20, TimeUnit.SECONDS); + + assertEquals(response.getStatusCode(), 200); + assertTrue(response.getResponseBody().isEmpty()); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 3000) + public void asyncStreamJustStatusLine() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + + server.enqueueEcho(); + + final int STATUS = 0; + final int COMPLETED = 1; + final int OTHER = 2; + final boolean[] whatCalled = {false, false, false}; + final CountDownLatch latch = new CountDownLatch(1); + Future statusCode = client.prepareGet(getTargetUrl()).execute(new AsyncHandler() { + private int status = -1; + + @Override + public void onThrowable(Throwable t) { + whatCalled[OTHER] = true; + latch.countDown(); + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart bodyPart) { + whatCalled[OTHER] = true; + latch.countDown(); + return State.ABORT; + } + + @Override + public State onStatusReceived(HttpResponseStatus responseStatus) { + whatCalled[STATUS] = true; + status = responseStatus.getStatusCode(); + latch.countDown(); + return State.ABORT; + } + + @Override + public State onHeadersReceived(HttpHeaders headers) { + whatCalled[OTHER] = true; + latch.countDown(); + return State.ABORT; + } + + @Override + public Integer onCompleted() { + whatCalled[COMPLETED] = true; + latch.countDown(); + return status; + } + }); + + if (!latch.await(2, TimeUnit.SECONDS)) { + fail("Timeout"); + return; + } + Integer status = statusCode.get(TIMEOUT, TimeUnit.SECONDS); + assertEquals((int) status, 200, "Expected status code failed."); + + if (!whatCalled[STATUS]) { + fail("onStatusReceived not called."); + } + if (!whatCalled[COMPLETED]) { + fail("onCompleted not called."); + } + if (whatCalled[OTHER]) { + fail("Other method of AsyncHandler got called."); + } + })); + } + + // This test is flaky - see https://github.com/AsyncHttpClient/async-http-client/issues/1728#issuecomment-699962325 + // For now, just run again if fails + @RepeatedIfExceptionsTest(repeats = 5) + public void asyncOptionsTest() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + + final AtomicReference responseHeaders = new AtomicReference<>(); + + // 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.google.com/").execute(new AsyncHandlerAdapter() { + + @Override + public State onHeadersReceived(HttpHeaders headers) { + responseHeaders.set(headers); + return State.ABORT; + } + + @Override + public String onCompleted() { + return "OK"; + } + }); + + f.get(20, TimeUnit.SECONDS); + HttpHeaders h = responseHeaders.get(); + assertNotNull(h); + if (h.contains(ALLOW)) { + String[] values = h.get(ALLOW).split(",|, "); + assertNotNull(values); + // Some responses contain the TRACE method, some do not - account for both + assert values.length == expected.length || values.length == expectedWithTrace.length; + Arrays.sort(values); + // Some responses contain the TRACE method, some do not - account for both + if (values.length == expected.length) { + assertArrayEquals(values, expected); + } else { + assertArrayEquals(values, expectedWithTrace); + } + } + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void closeConnectionTest() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + + Response r = client.prepareGet(getTargetUrl()).execute(new AsyncHandler() { + + private final Response.ResponseBuilder builder = new Response.ResponseBuilder(); + + @Override + public State onHeadersReceived(HttpHeaders headers) { + builder.accumulate(headers); + return State.CONTINUE; + } + + @Override + public void onThrowable(Throwable t) { + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart content) { + builder.accumulate(content); + return content.isLast() ? State.ABORT : State.CONTINUE; + } + + @Override + public State onStatusReceived(HttpResponseStatus responseStatus) { + builder.accumulate(responseStatus); + + return State.CONTINUE; + } + + @Override + public Response onCompleted() { + return builder.build(); + } + }).get(); + + assertNotNull(r); + assertEquals(r.getStatusCode(), 200); + })); + } +} diff --git a/client/src/test/java/org/asynchttpclient/AsyncStreamLifecycleTest.java b/client/src/test/java/org/asynchttpclient/AsyncStreamLifecycleTest.java new file mode 100644 index 0000000000..9b290f82ed --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/AsyncStreamLifecycleTest.java @@ -0,0 +1,148 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.HttpHeaders; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.junit.jupiter.api.AfterAll; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Tests default asynchronous life cycle. + * + * @author Hubert Iwaniuk + */ +public class AsyncStreamLifecycleTest extends AbstractBasicTest { + private static final ExecutorService executorService = Executors.newFixedThreadPool(2); + + @Override + @AfterAll + public void tearDownGlobal() throws Exception { + super.tearDownGlobal(); + executorService.shutdownNow(); + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new AbstractHandler() { + @Override + public void handle(String s, Request request, HttpServletRequest req, final HttpServletResponse resp) throws IOException { + resp.setContentType("text/plain;charset=utf-8"); + resp.setStatus(200); + final AsyncContext asyncContext = request.startAsync(); + final PrintWriter writer = resp.getWriter(); + executorService.submit(() -> { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + logger.error("Failed to sleep for 100 ms.", e); + } + logger.info("Delivering part1."); + writer.write("part1"); + writer.flush(); + }); + executorService.submit(() -> { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + logger.error("Failed to sleep for 200 ms.", e); + } + logger.info("Delivering part2."); + writer.write("part2"); + writer.flush(); + asyncContext.complete(); + }); + request.setHandled(true); + } + }; + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testStream() throws Exception { + try (AsyncHttpClient ahc = asyncHttpClient()) { + final AtomicBoolean err = new AtomicBoolean(false); + final LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); + final AtomicBoolean status = new AtomicBoolean(false); + final AtomicInteger headers = new AtomicInteger(0); + final CountDownLatch latch = new CountDownLatch(1); + ahc.executeRequest(ahc.prepareGet(getTargetUrl()).build(), new AsyncHandler() { + @Override + public void onThrowable(Throwable t) { + fail("Got throwable.", t); + err.set(true); + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart e) throws Exception { + if (e.length() != 0) { + String s = new String(e.getBodyPartBytes()); + logger.info("got part: {}", s); + queue.put(s); + } + return State.CONTINUE; + } + + @Override + public State onStatusReceived(HttpResponseStatus e) { + status.set(true); + return State.CONTINUE; + } + + @Override + public State onHeadersReceived(HttpHeaders e) throws Exception { + if (headers.incrementAndGet() == 2) { + throw new Exception("Analyze this."); + } + return State.CONTINUE; + } + + @Override + public Object onCompleted() { + latch.countDown(); + return null; + } + }); + + assertTrue(latch.await(1, TimeUnit.SECONDS), "Latch failed."); + assertFalse(err.get()); + assertEquals(queue.size(), 2); + assertTrue(queue.contains("part1")); + assertTrue(queue.contains("part2")); + assertTrue(status.get()); + assertEquals(headers.get(), 1); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/AuthTimeoutTest.java b/client/src/test/java/org/asynchttpclient/AuthTimeoutTest.java new file mode 100644 index 0000000000..e23328d7a4 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/AuthTimeoutTest.java @@ -0,0 +1,201 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +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; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; + +import java.io.IOException; +import java.io.OutputStream; +import java.time.Duration; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.basicAuthRealm; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.digestAuthRealm; +import static org.asynchttpclient.test.TestUtils.ADMIN; +import static org.asynchttpclient.test.TestUtils.USER; +import static org.asynchttpclient.test.TestUtils.addBasicAuthHandler; +import static org.asynchttpclient.test.TestUtils.addDigestAuthHandler; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class AuthTimeoutTest extends AbstractBasicTest { + + private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(1); + private static final int SHORT_FUTURE_TIMEOUT = 500; // shorter than REQUEST_TIMEOUT + private static final int LONG_FUTURE_TIMEOUT = 1500; // longer than REQUEST_TIMEOUT + + private Server server2; + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector1 = addHttpConnector(server); + addBasicAuthHandler(server, configureHandler()); + server.start(); + port1 = connector1.getLocalPort(); + + server2 = new Server(); + ServerConnector connector2 = addHttpConnector(server2); + addDigestAuthHandler(server2, configureHandler()); + server2.start(); + port2 = connector2.getLocalPort(); + + logger.info("Local HTTP server started successfully"); + } + + @Override + @AfterEach + public void tearDownGlobal() throws Exception { + super.tearDownGlobal(); + server2.stop(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicAuthTimeoutTest() throws Throwable { + try (AsyncHttpClient client = newClient()) { + execute(client, true, false).get(LONG_FUTURE_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (Exception ex) { + assertInstanceOf(TimeoutException.class, ex.getCause()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicPreemptiveAuthTimeoutTest() throws Throwable { + try (AsyncHttpClient client = newClient()) { + execute(client, true, true).get(LONG_FUTURE_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (Exception ex) { + assertInstanceOf(TimeoutException.class, ex.getCause()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void digestAuthTimeoutTest() throws Throwable { + try (AsyncHttpClient client = newClient()) { + execute(client, false, false).get(LONG_FUTURE_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (Exception ex) { + assertInstanceOf(TimeoutException.class, ex.getCause()); + } + } + + @Disabled + @RepeatedIfExceptionsTest(repeats = 5) + public void digestPreemptiveAuthTimeoutTest() throws Throwable { + try (AsyncHttpClient client = newClient()) { + assertThrows(TimeoutException.class, () -> execute(client, false, true).get(LONG_FUTURE_TIMEOUT, TimeUnit.MILLISECONDS)); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicAuthFutureTimeoutTest() throws Throwable { + try (AsyncHttpClient client = newClient()) { + assertThrows(TimeoutException.class, () -> execute(client, true, false).get(SHORT_FUTURE_TIMEOUT, TimeUnit.MILLISECONDS)); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicPreemptiveAuthFutureTimeoutTest() throws Throwable { + try (AsyncHttpClient client = newClient()) { + assertThrows(TimeoutException.class, () -> execute(client, true, true).get(SHORT_FUTURE_TIMEOUT, TimeUnit.MILLISECONDS)); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void digestAuthFutureTimeoutTest() throws Throwable { + try (AsyncHttpClient client = newClient()) { + assertThrows(TimeoutException.class, () -> execute(client, false, false).get(SHORT_FUTURE_TIMEOUT, TimeUnit.MILLISECONDS)); + } + } + + @Disabled + @RepeatedIfExceptionsTest(repeats = 5) + public void digestPreemptiveAuthFutureTimeoutTest() throws Throwable { + try (AsyncHttpClient client = newClient()) { + assertThrows(TimeoutException.class, () -> execute(client, false, true).get(SHORT_FUTURE_TIMEOUT, TimeUnit.MILLISECONDS)); + } + } + + private static AsyncHttpClient newClient() { + return asyncHttpClient(config().setRequestTimeout(REQUEST_TIMEOUT)); + } + + protected Future execute(AsyncHttpClient client, boolean basic, boolean preemptive) { + Realm.Builder realm; + String url; + + if (basic) { + realm = basicAuthRealm(USER, ADMIN); + url = getTargetUrl(); + } else { + realm = digestAuthRealm(USER, ADMIN); + url = getTargetUrl2(); + if (preemptive) { + realm.setRealmName("MyRealm"); + realm.setAlgorithm("MD5"); + realm.setQop("auth"); + realm.setNonce("fFDVc60re9zt8fFDvht0tNrYuvqrcchN"); + } + } + + return client.prepareGet(url).setRealm(realm.setUsePreemptiveAuth(preemptive).build()).execute(); + } + + @Override + protected String getTargetUrl() { + return "http://localhost/" + port1 + '/'; + } + + @Override + protected String getTargetUrl2() { + return "http://localhost/" + port2 + '/'; + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new IncompleteResponseHandler(); + } + + private static class IncompleteResponseHandler extends AbstractHandler { + + @Override + public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + // NOTE: handler sends less bytes than are given in Content-Length, which should lead to timeout + response.setStatus(200); + OutputStream out = response.getOutputStream(); + response.setIntHeader(CONTENT_LENGTH.toString(), 1000); + out.write(0); + out.flush(); + try { + Thread.sleep(LONG_FUTURE_TIMEOUT + 100); + } catch (InterruptedException e) { + // + } + } + } +} 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 0000000000..8f57ffb88f --- /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/BasicAuthTest.java b/client/src/test/java/org/asynchttpclient/BasicAuthTest.java new file mode 100644 index 0000000000..af1ee7b57a --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/BasicAuthTest.java @@ -0,0 +1,349 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.HttpHeaders; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +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; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.basicAuthRealm; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.ADMIN; +import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE; +import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE_STRING; +import static org.asynchttpclient.test.TestUtils.USER; +import static org.asynchttpclient.test.TestUtils.addBasicAuthHandler; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class BasicAuthTest extends AbstractBasicTest { + + private Server server2; + private Server serverNoAuth; + private int portNoAuth; + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector1 = addHttpConnector(server); + addBasicAuthHandler(server, configureHandler()); + server.start(); + port1 = connector1.getLocalPort(); + + server2 = new Server(); + ServerConnector connector2 = addHttpConnector(server2); + addBasicAuthHandler(server2, new RedirectHandler()); + server2.start(); + port2 = connector2.getLocalPort(); + + // need noAuth server to verify the preemptive auth mode (see basicAuthTestPreemptiveTest) + serverNoAuth = new Server(); + ServerConnector connectorNoAuth = addHttpConnector(serverNoAuth); + serverNoAuth.setHandler(new SimpleHandler()); + serverNoAuth.start(); + portNoAuth = connectorNoAuth.getLocalPort(); + + logger.info("Local HTTP server started successfully"); + } + + @Override + @AfterEach + public void tearDownGlobal() throws Exception { + super.tearDownGlobal(); + server2.stop(); + serverNoAuth.stop(); + } + + @Override + protected String getTargetUrl() { + return "http://localhost/" + port1 + '/'; + } + + @Override + protected String getTargetUrl2() { + return "http://localhost/" + port2 + "/uff"; + } + + private String getTargetUrlNoAuth() { + return "http://localhost/" + portNoAuth + '/'; + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new SimpleHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicAuthTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.prepareGet(getTargetUrl()) + .setRealm(basicAuthRealm(USER, ADMIN).build()) + .execute(); + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertNotNull(resp.getHeader("X-Auth")); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void redirectAndBasicAuthTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().setFollowRedirect(true).setMaxRedirects(10))) { + Future f = client.prepareGet(getTargetUrl2()) + .setRealm(basicAuthRealm(USER, ADMIN).build()) + .execute(); + Response resp = f.get(3, TimeUnit.SECONDS); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertNotNull(resp); + assertNotNull(resp.getHeader("X-Auth")); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basic401Test() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + BoundRequestBuilder r = client.prepareGet(getTargetUrl()) + .setHeader("X-401", "401") + .setRealm(basicAuthRealm(USER, ADMIN).build()); + + Future f = r.execute(new AsyncHandler() { + + private HttpResponseStatus status; + + @Override + public void onThrowable(Throwable t) { + + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart bodyPart) { + return State.CONTINUE; + } + + @Override + public State onStatusReceived(HttpResponseStatus responseStatus) { + status = responseStatus; + + if (status.getStatusCode() != 200) { + return State.ABORT; + } + return State.CONTINUE; + } + + @Override + public State onHeadersReceived(HttpHeaders headers) { + return State.CONTINUE; + } + + @Override + public Integer onCompleted() { + return status.getStatusCode(); + } + }); + Integer statusCode = f.get(10, TimeUnit.SECONDS); + assertNotNull(statusCode); + assertEquals(statusCode.intValue(), 401); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicAuthTestPreemptiveTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + // send the request to the no-auth endpoint to be able to verify the + // auth header is really sent preemptively for the initial call. + Future f = client.prepareGet(getTargetUrlNoAuth()) + .setRealm(basicAuthRealm(USER, ADMIN).setUsePreemptiveAuth(true).build()) + .execute(); + + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertNotNull(resp.getHeader("X-Auth")); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicAuthNegativeTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.prepareGet(getTargetUrl()) + .setRealm(basicAuthRealm("fake", ADMIN).build()) + .execute(); + + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), 401); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicAuthInputStreamTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.preparePost(getTargetUrl()) + .setBody(new ByteArrayInputStream("test".getBytes())) + .setRealm(basicAuthRealm(USER, ADMIN).build()) + .execute(); + + Response resp = f.get(30, TimeUnit.SECONDS); + assertNotNull(resp); + assertNotNull(resp.getHeader("X-Auth")); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getResponseBody(), "test"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicAuthFileTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.preparePost(getTargetUrl()) + .setBody(SIMPLE_TEXT_FILE) + .setRealm(basicAuthRealm(USER, ADMIN).build()) + .execute(); + + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertNotNull(resp.getHeader("X-Auth")); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicAuthAsyncConfigTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().setRealm(basicAuthRealm(USER, ADMIN)))) { + Future f = client.preparePost(getTargetUrl()) + .setBody(SIMPLE_TEXT_FILE_STRING) + .execute(); + + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertNotNull(resp.getHeader("X-Auth")); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicAuthFileNoKeepAliveTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().setKeepAlive(false))) { + + Future f = client.preparePost(getTargetUrl()) + .setBody(SIMPLE_TEXT_FILE) + .setRealm(basicAuthRealm(USER, ADMIN).build()) + .execute(); + + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertNotNull(resp.getHeader("X-Auth")); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void noneAuthTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + BoundRequestBuilder r = client.prepareGet(getTargetUrl()).setRealm(basicAuthRealm(USER, ADMIN).build()); + + Future f = r.execute(); + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertNotNull(resp.getHeader("X-Auth")); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + } + } + + private static class RedirectHandler extends AbstractHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(RedirectHandler.class); + + @Override + public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + + LOGGER.info("request: " + request.getRequestURI()); + + if ("/uff".equals(request.getRequestURI())) { + LOGGER.info("redirect to /bla"); + response.setStatus(302); + response.setContentLength(0); + response.setHeader("Location", "/bla"); + + } else { + LOGGER.info("got redirected" + request.getRequestURI()); + response.setStatus(200); + response.addHeader("X-Auth", request.getHeader("Authorization")); + response.addHeader("X-" + CONTENT_LENGTH, String.valueOf(request.getContentLength())); + byte[] b = "content".getBytes(UTF_8); + response.setContentLength(b.length); + response.getOutputStream().write(b); + } + response.getOutputStream().flush(); + response.getOutputStream().close(); + } + } + + public static class SimpleHandler extends AbstractHandler { + + @Override + public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + + if (request.getHeader("X-401") != null) { + response.setStatus(401); + response.setContentLength(0); + + } else { + response.addHeader("X-Auth", request.getHeader("Authorization")); + response.addHeader("X-" + CONTENT_LENGTH, String.valueOf(request.getContentLength())); + response.setIntHeader("X-" + CONTENT_LENGTH, request.getContentLength()); + response.setStatus(200); + + int size = 10 * 1024; + byte[] bytes = new byte[size]; + int contentLength = 0; + int read; + do { + read = request.getInputStream().read(bytes); + if (read > 0) { + contentLength += read; + response.getOutputStream().write(bytes, 0, read); + } + } while (read >= 0); + response.setContentLength(contentLength); + } + response.getOutputStream().flush(); + response.getOutputStream().close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/BasicHttpProxyToHttpTest.java b/client/src/test/java/org/asynchttpclient/BasicHttpProxyToHttpTest.java new file mode 100644 index 0000000000..6845152d85 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/BasicHttpProxyToHttpTest.java @@ -0,0 +1,135 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.Realm.AuthScheme; +import org.asynchttpclient.test.EchoHandler; +import org.eclipse.jetty.proxy.ProxyServlet; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.concurrent.Future; + +import static io.netty.handler.codec.http.HttpHeaderNames.PROXY_AUTHENTICATE; +import static io.netty.handler.codec.http.HttpHeaderNames.PROXY_AUTHORIZATION; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.get; +import static org.asynchttpclient.Dsl.proxyServer; +import static org.asynchttpclient.Dsl.realm; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test that validates that when having an HTTP proxy and trying to access an HTTP through the proxy the proxy credentials should be passed after it gets a 407 response. + */ +public class BasicHttpProxyToHttpTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(BasicHttpProxyToHttpTest.class); + + private int httpPort; + private int proxyPort; + + private Server httpServer; + private Server proxy; + + @BeforeEach + public void setUpGlobal() throws Exception { + httpServer = new Server(); + ServerConnector connector1 = addHttpConnector(httpServer); + httpServer.setHandler(new EchoHandler()); + httpServer.start(); + httpPort = connector1.getLocalPort(); + + proxy = new Server(); + ServerConnector connector2 = addHttpConnector(proxy); + ServletHandler servletHandler = new ServletHandler(); + ServletHolder servletHolder = servletHandler.addServletWithMapping(BasicAuthProxyServlet.class, "/*"); + servletHolder.setInitParameter("maxThreads", "20"); + proxy.setHandler(servletHandler); + proxy.start(); + proxyPort = connector2.getLocalPort(); + + LOGGER.info("Local HTTP Server (" + httpPort + "), Proxy (" + proxyPort + ") started successfully"); + } + + @AfterEach + public void tearDownGlobal() { + if (proxy != null) { + try { + proxy.stop(); + } catch (Exception e) { + LOGGER.error("Failed to properly close proxy", e); + } + } + if (httpServer != null) { + try { + httpServer.stop(); + } catch (Exception e) { + LOGGER.error("Failed to properly close server", e); + } + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void nonPreemptiveProxyAuthWithPlainHttpTarget() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + String targetUrl = "http://localhost/" + httpPort + "/foo/bar"; + Request request = get(targetUrl) + .setProxyServer(proxyServer("127.0.0.1", proxyPort).setRealm(realm(AuthScheme.BASIC, "johndoe", "pass"))) + // .setRealm(realm(AuthScheme.BASIC, "user", "passwd")) + .build(); + Future responseFuture = client.executeRequest(request); + Response response = responseFuture.get(); + + assertEquals(response.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals("/foo/bar", response.getHeader("X-pathInfo")); + } + } + + @SuppressWarnings("serial") + public static class BasicAuthProxyServlet extends ProxyServlet { + + @Override + protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { + LOGGER.debug(">>> got a request !"); + + String authorization = request.getHeader(PROXY_AUTHORIZATION.toString()); + if (authorization == null) { + response.setStatus(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED); + response.setHeader(PROXY_AUTHENTICATE.toString(), "Basic realm=\"Fake Realm\""); + response.getOutputStream().flush(); + + } else if ("Basic am9obmRvZTpwYXNz".equals(authorization)) { + super.service(request, response); + + } else { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getOutputStream().flush(); + } + } + } +} \ No newline at end of file diff --git a/client/src/test/java/org/asynchttpclient/BasicHttpProxyToHttpsTest.java b/client/src/test/java/org/asynchttpclient/BasicHttpProxyToHttpsTest.java new file mode 100644 index 0000000000..51d24af7c4 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/BasicHttpProxyToHttpsTest.java @@ -0,0 +1,129 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.Realm.AuthScheme; +import org.asynchttpclient.test.EchoHandler; +import org.eclipse.jetty.proxy.ConnectHandler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.Future; + +import static io.netty.handler.codec.http.HttpHeaderNames.PROXY_AUTHENTICATE; +import static io.netty.handler.codec.http.HttpHeaderNames.PROXY_AUTHORIZATION; +import static io.netty.handler.codec.http.HttpHeaderNames.USER_AGENT; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.get; +import static org.asynchttpclient.Dsl.proxyServer; +import static org.asynchttpclient.Dsl.realm; +import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUserAgent; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.asynchttpclient.test.TestUtils.addHttpsConnector; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test that validates that when having an HTTP proxy and trying to access an HTTPS + * through the proxy the proxy credentials and a custom user-agent (if set) should be passed during the CONNECT request. + */ +public class BasicHttpProxyToHttpsTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(BasicHttpProxyToHttpsTest.class); + private static final String CUSTOM_USER_AGENT = "custom-user-agent"; + + private int httpPort; + private int proxyPort; + + private Server httpServer; + private Server proxy; + + @BeforeEach + public void setUpGlobal() throws Exception { + // HTTP server + httpServer = new Server(); + ServerConnector connector1 = addHttpsConnector(httpServer); + httpServer.setHandler(new EchoHandler()); + httpServer.start(); + httpPort = connector1.getLocalPort(); + + // proxy + proxy = new Server(); + ServerConnector connector2 = addHttpConnector(proxy); + ConnectHandler connectHandler = new ConnectHandler() { + + @Override + // This proxy receives a CONNECT request from the client before making the real request for the target host. + protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) { + + // If the userAgent of the CONNECT request is the same as the default userAgent, + // then the custom userAgent was not properly propagated and the test should fail. + String userAgent = request.getHeader(USER_AGENT.toString()); + if (userAgent.equals(defaultUserAgent())) { + return false; + } + + // If the authentication failed, the test should also fail. + String authorization = request.getHeader(PROXY_AUTHORIZATION.toString()); + if (authorization == null) { + response.setStatus(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED); + response.setHeader(PROXY_AUTHENTICATE.toString(), "Basic realm=\"Fake Realm\""); + return false; + } + if ("Basic am9obmRvZTpwYXNz".equals(authorization)) { + return true; + } + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + }; + proxy.setHandler(connectHandler); + proxy.start(); + proxyPort = connector2.getLocalPort(); + + LOGGER.info("Local HTTP Server (" + httpPort + "), Proxy (" + proxyPort + ") started successfully"); + } + + @AfterEach + public void tearDownGlobal() throws Exception { + httpServer.stop(); + proxy.stop(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void nonPreemptiveProxyAuthWithHttpsTarget() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().setUseInsecureTrustManager(true))) { + String targetUrl = "https://localhost/" + httpPort + "/foo/bar"; + Request request = get(targetUrl) + .setProxyServer(proxyServer("127.0.0.1", proxyPort).setRealm(realm(AuthScheme.BASIC, "johndoe", "pass"))) + .setHeader("user-agent", CUSTOM_USER_AGENT) + // .setRealm(realm(AuthScheme.BASIC, "user", "passwd")) + .build(); + Future responseFuture = client.executeRequest(request); + Response response = responseFuture.get(); + + assertEquals(response.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals("/foo/bar", response.getHeader("X-pathInfo")); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/BasicHttpTest.java b/client/src/test/java/org/asynchttpclient/BasicHttpTest.java new file mode 100755 index 0000000000..f83cac80f4 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/BasicHttpTest.java @@ -0,0 +1,1043 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.cookie.Cookie; +import io.netty.handler.codec.http.cookie.DefaultCookie; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.handler.MaxRedirectException; +import org.asynchttpclient.request.body.generator.InputStreamBodyGenerator; +import org.asynchttpclient.request.body.multipart.StringPart; +import org.asynchttpclient.test.EventCollectingHandler; +import org.asynchttpclient.testserver.HttpServer; +import org.asynchttpclient.testserver.HttpServer.EchoHandler; +import org.asynchttpclient.testserver.HttpTest; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import javax.net.ssl.SSLException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.ConnectException; +import java.net.URLDecoder; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.netty.handler.codec.http.HttpHeaderNames.HOST; +import static io.netty.handler.codec.http.HttpHeaderNames.TRANSFER_ENCODING; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.get; +import static org.asynchttpclient.Dsl.head; +import static org.asynchttpclient.Dsl.post; +import static org.asynchttpclient.test.TestUtils.AsyncCompletionHandlerAdapter; +import static org.asynchttpclient.test.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; +import static org.asynchttpclient.test.TestUtils.TIMEOUT; +import static org.asynchttpclient.test.TestUtils.assertContentTypesEquals; +import static org.asynchttpclient.test.TestUtils.findFreePort; +import static org.asynchttpclient.test.TestUtils.writeResponseBody; +import static org.asynchttpclient.util.DateUtils.unpreciseMillisTime; +import static org.asynchttpclient.util.ThrowableUtil.unknownStackTrace; +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; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class BasicHttpTest extends HttpTest { + + public static final byte[] ACTUAL = {}; + private HttpServer server; + + @BeforeEach + public void start() throws Throwable { + server = new HttpServer(); + server.start(); + } + + @AfterEach + public void stop() throws Throwable { + server.close(); + } + + private String getTargetUrl() { + return server.getHttpUrl() + "/foo/bar"; + } + + @RepeatedIfExceptionsTest(repeats = 5) + 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); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void getUrlWithPathWithoutQuery() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueOk(); + + Response response = client.executeRequest(get(getTargetUrl()), new AsyncCompletionHandlerAdapter()).get(TIMEOUT, SECONDS); + assertEquals(response.getUri().toUrl(), getTargetUrl()); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void getUrlWithPathWithQuery() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + String targetUrl = getTargetUrl() + "?q=+%20x"; + Request request = get(targetUrl).build(); + assertEquals(request.getUrl(), targetUrl); + server.enqueueOk(); + + Response response = client.executeRequest(request, new AsyncCompletionHandlerAdapter()).get(TIMEOUT, SECONDS); + assertEquals(response.getUri().toUrl(), targetUrl); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void getUrlWithPathWithQueryParams() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueOk(); + + Response response = client.executeRequest(get(getTargetUrl()).addQueryParam("q", "a b"), new AsyncCompletionHandlerAdapter()).get(TIMEOUT, SECONDS); + assertEquals(response.getUri().toUrl(), getTargetUrl() + "?q=a%20b"); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void getResponseBody() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + final String body = "Hello World"; + + server.enqueueResponse(response -> { + response.setStatus(200); + response.setContentType(TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + writeResponseBody(response, body); + }); + + client.executeRequest(get(getTargetUrl()), new AsyncCompletionHandlerAdapter() { + + @Override + public Response onCompleted(Response response) { + assertEquals(response.getStatusCode(), 200); + String contentLengthHeader = response.getHeader(CONTENT_LENGTH); + assertNotNull(contentLengthHeader); + assertEquals(Integer.parseInt(contentLengthHeader), body.length()); + assertContentTypesEquals(response.getContentType(), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + assertEquals(response.getResponseBody(), body); + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void getWithHeaders() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + HttpHeaders h = new DefaultHttpHeaders(); + for (int i = 1; i < 5; i++) { + h.add("Test" + i, "Test" + i); + } + + server.enqueueEcho(); + + client.executeRequest(get(getTargetUrl()).setHeaders(h), new AsyncCompletionHandlerAdapter() { + + @Override + public Response onCompleted(Response response) { + assertEquals(response.getStatusCode(), 200); + for (int i = 1; i < 5; i++) { + assertEquals(response.getHeader("X-Test" + i), "Test" + i); + } + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postWithHeadersAndFormParams() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + HttpHeaders h = new DefaultHttpHeaders(); + h.add(CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + + Map> m = new HashMap<>(); + for (int i = 0; i < 5; i++) { + m.put("param_" + i, Collections.singletonList("value_" + i)); + } + + Request request = post(getTargetUrl()).setHeaders(h).setFormParams(m).build(); + + server.enqueueEcho(); + + client.executeRequest(request, new AsyncCompletionHandlerAdapter() { + + @Override + public Response onCompleted(Response response) { + assertEquals(response.getStatusCode(), 200); + for (int i = 1; i < 5; i++) { + assertEquals(response.getHeader("X-param_" + i), "value_" + i); + } + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postChineseChar() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + HttpHeaders h = new DefaultHttpHeaders(); + h.add(CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + + String chineseChar = "是"; + + Map> m = new HashMap<>(); + m.put("param", Collections.singletonList(chineseChar)); + + Request request = post(getTargetUrl()).setHeaders(h).setFormParams(m).build(); + + server.enqueueEcho(); + + client.executeRequest(request, new AsyncCompletionHandlerAdapter() { + @Override + public Response onCompleted(Response response) { + assertEquals(response.getStatusCode(), 200); + String value; + // headers must be encoded + value = URLDecoder.decode(response.getHeader("X-param"), UTF_8); + assertEquals(value, chineseChar); + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void headHasEmptyBody() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueOk(); + + Response response = client.executeRequest(head(getTargetUrl()), new AsyncCompletionHandlerAdapter() { + @Override + public Response onCompleted(Response response) { + assertEquals(response.getStatusCode(), 200); + return response; + } + }).get(TIMEOUT, SECONDS); + + assertTrue(response.getResponseBody().isEmpty()); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void nullSchemeThrowsNPE() throws Throwable { + assertThrows(IllegalArgumentException.class, () -> withClient().run(client -> client.prepareGet("gatling.io").execute())); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void jettyRespondsWithChunkedTransferEncoding() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + client.prepareGet(getTargetUrl()) + .execute(new AsyncCompletionHandlerAdapter() { + @Override + public Response onCompleted(Response response) { + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getHeader(TRANSFER_ENCODING), HttpHeaderValues.CHUNKED.toString()); + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void getWithCookies() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + final Cookie coo = new DefaultCookie("foo", "value"); + coo.setDomain("/"); + coo.setPath("/"); + server.enqueueEcho(); + + client.prepareGet(getTargetUrl()) + .addCookie(coo) + .execute(new AsyncCompletionHandlerAdapter() { + @Override + public Response onCompleted(Response response) { + assertEquals(response.getStatusCode(), 200); + List cookies = response.getCookies(); + assertEquals(cookies.size(), 1); + assertEquals(cookies.get(0).toString(), "foo=value"); + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void defaultRequestBodyEncodingIsUtf8() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + Response response = client.preparePost(getTargetUrl()) + .setBody("\u017D\u017D\u017D\u017D\u017D\u017D") + .execute().get(); + assertArrayEquals(response.getResponseBodyAsBytes(), "\u017D\u017D\u017D\u017D\u017D\u017D".getBytes(UTF_8)); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postFormParametersAsBodyString() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + HttpHeaders h = new DefaultHttpHeaders(); + h.add(CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 5; i++) { + sb.append("param_").append(i).append("=value_").append(i).append('&'); + } + sb.setLength(sb.length() - 1); + + server.enqueueEcho(); + client.preparePost(getTargetUrl()) + .setHeaders(h) + .setBody(sb.toString()) + .execute(new AsyncCompletionHandlerAdapter() { + + @Override + public Response onCompleted(Response response) { + assertEquals(response.getStatusCode(), 200); + for (int i = 1; i < 5; i++) { + assertEquals(response.getHeader("X-param_" + i), "value_" + i); + + } + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postFormParametersAsBodyStream() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + HttpHeaders h = new DefaultHttpHeaders(); + h.add(CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 5; i++) { + sb.append("param_").append(i).append("=value_").append(i).append('&'); + } + sb.setLength(sb.length() - 1); + + server.enqueueEcho(); + client.preparePost(getTargetUrl()) + .setHeaders(h) + .setBody(new ByteArrayInputStream(sb.toString().getBytes(UTF_8))) + .execute(new AsyncCompletionHandlerAdapter() { + + @Override + public Response onCompleted(Response response) { + assertEquals(response.getStatusCode(), 200); + for (int i = 1; i < 5; i++) { + assertEquals(response.getHeader("X-param_" + i), "value_" + i); + + } + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void putFormParametersAsBodyStream() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + HttpHeaders h = new DefaultHttpHeaders(); + h.add(CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 5; i++) { + sb.append("param_").append(i).append("=value_").append(i).append('&'); + } + sb.setLength(sb.length() - 1); + ByteArrayInputStream is = new ByteArrayInputStream(sb.toString().getBytes()); + + server.enqueueEcho(); + client.preparePut(getTargetUrl()) + .setHeaders(h) + .setBody(is) + .execute(new AsyncCompletionHandlerAdapter() { + + @Override + public Response onCompleted(Response response) { + assertEquals(response.getStatusCode(), 200); + for (int i = 1; i < 5; i++) { + assertEquals(response.getHeader("X-param_" + i), "value_" + i); + } + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postSingleStringPart() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + client.preparePost(getTargetUrl()) + .addBodyPart(new StringPart("foo", "bar")) + .execute(new AsyncCompletionHandlerAdapter() { + @Override + public Response onCompleted(Response response) { + String requestContentType = response.getHeader("X-" + CONTENT_TYPE); + String boundary = requestContentType.substring(requestContentType.indexOf("boundary") + "boundary".length() + 1); + assertTrue(response.getResponseBody().regionMatches(false, "--".length(), boundary, 0, boundary.length())); + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postWithBody() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + client.preparePost(getTargetUrl()) + .execute(new AsyncCompletionHandlerAdapter() { + @Override + public Response onCompleted(Response response) { + assertEquals(response.getHeader("X-" + CONTENT_LENGTH), "0"); + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void getVirtualHost() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + String virtualHost = "localhost:" + server.getHttpPort(); + + server.enqueueEcho(); + Response response = client.prepareGet(getTargetUrl()) + .setVirtualHost(virtualHost) + .execute(new AsyncCompletionHandlerAdapter()).get(TIMEOUT, SECONDS); + + assertEquals(response.getStatusCode(), 200); + if (response.getHeader("X-" + HOST) == null) { + System.err.println(response); + } + assertEquals(response.getHeader("X-" + HOST), virtualHost); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void cancelledFutureThrowsCancellationException() throws Throwable { + assertThrows(CancellationException.class, () -> { + withClient().run(client -> + withServer(server).run(server -> { + HttpHeaders headers = new DefaultHttpHeaders(); + headers.add("X-Delay", 5_000); + server.enqueueEcho(); + + Future future = client.prepareGet(getTargetUrl()).setHeaders(headers).execute(new AsyncCompletionHandlerAdapter() { + @Override + public void onThrowable(Throwable t) { + } + }); + future.cancel(true); + future.get(TIMEOUT, SECONDS); + })); + }); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void futureTimeOutThrowsTimeoutException() throws Throwable { + assertThrows(TimeoutException.class, () -> { + withClient().run(client -> + withServer(server).run(server -> { + HttpHeaders headers = new DefaultHttpHeaders(); + headers.add("X-Delay", 5_000); + + server.enqueueEcho(); + Future future = client.prepareGet(getTargetUrl()).setHeaders(headers).execute(new AsyncCompletionHandlerAdapter() { + @Override + public void onThrowable(Throwable t) { + } + }); + + future.get(2, SECONDS); + })); + }); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void connectFailureThrowsConnectException() throws Throwable { + assertThrows(ConnectException.class, () -> { + withClient().run(client -> { + int dummyPort = findFreePort(); + try { + client.preparePost(String.format("http://localhost:%d/", dummyPort)).execute(new AsyncCompletionHandlerAdapter() { + @Override + public void onThrowable(Throwable t) { + } + }).get(TIMEOUT, SECONDS); + } catch (ExecutionException ex) { + throw ex.getCause(); + } + }); + }); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void connectFailureNotifiesHandlerWithConnectException() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + final CountDownLatch l = new CountDownLatch(1); + int port = findFreePort(); + + client.prepareGet(String.format("http://localhost:%d/", port)).execute(new AsyncCompletionHandlerAdapter() { + @Override + public void onThrowable(Throwable t) { + try { + assertInstanceOf(ConnectException.class, t); + } finally { + l.countDown(); + } + } + }); + + if (!l.await(TIMEOUT, SECONDS)) { + fail("Timed out"); + } + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void unknownHostThrowsUnknownHostException() throws Throwable { + assertThrows(UnknownHostException.class, () -> { + withClient().run(client -> + withServer(server).run(server -> { + try { + client.prepareGet("http://null.gatling.io/").execute(new AsyncCompletionHandlerAdapter() { + @Override + public void onThrowable(Throwable t) { + } + }).get(TIMEOUT, SECONDS); + } catch (ExecutionException e) { + throw e.getCause(); + } + })); + }); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void getEmptyBody() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueOk(); + Response response = client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerAdapter()) + .get(TIMEOUT, SECONDS); + assertTrue(response.getResponseBody().isEmpty()); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void getEmptyBodyNotifiesHandler() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + final AtomicBoolean handlerWasNotified = new AtomicBoolean(); + + server.enqueueOk(); + client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerAdapter() { + + @Override + public Response onCompleted(Response response) { + assertEquals(response.getStatusCode(), 200); + handlerWasNotified.set(true); + return response; + } + }).get(TIMEOUT, SECONDS); + assertTrue(handlerWasNotified.get()); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void exceptionInOnCompletedGetNotifiedToOnThrowable() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference message = new AtomicReference<>(); + + server.enqueueOk(); + client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerAdapter() { + @Override + public Response onCompleted(Response response) { + throw unknownStackTrace(new IllegalStateException("FOO"), BasicHttpTest.class, "exceptionInOnCompletedGetNotifiedToOnThrowable"); + + } + + @Override + public void onThrowable(Throwable t) { + message.set(t.getMessage()); + latch.countDown(); + } + }); + + if (!latch.await(TIMEOUT, SECONDS)) { + fail("Timed out"); + } + + assertEquals(message.get(), "FOO"); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void exceptionInOnCompletedGetNotifiedToFuture() throws Throwable { + assertThrows(IllegalStateException.class, () -> { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueOk(); + Future whenResponse = client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerAdapter() { + @Override + public Response onCompleted(Response response) { + throw unknownStackTrace(new IllegalStateException("FOO"), BasicHttpTest.class, "exceptionInOnCompletedGetNotifiedToFuture"); + } + + @Override + public void onThrowable(Throwable t) { + } + }); + + try { + whenResponse.get(TIMEOUT, SECONDS); + } catch (ExecutionException e) { + throw e.getCause(); + } + })); + }); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void configTimeoutNotifiesOnThrowableAndFuture() throws Throwable { + assertThrows(TimeoutException.class, () -> { + withClient(config().setRequestTimeout(Duration.ofSeconds(1))).run(client -> + withServer(server).run(server -> { + HttpHeaders headers = new DefaultHttpHeaders(); + headers.add("X-Delay", 5_000); // delay greater than timeout + + final AtomicBoolean onCompletedWasNotified = new AtomicBoolean(); + final AtomicBoolean onThrowableWasNotifiedWithTimeoutException = new AtomicBoolean(); + final CountDownLatch latch = new CountDownLatch(1); + + server.enqueueEcho(); + Future whenResponse = client.prepareGet(getTargetUrl()).setHeaders(headers).execute(new AsyncCompletionHandlerAdapter() { + + @Override + public Response onCompleted(Response response) { + onCompletedWasNotified.set(true); + latch.countDown(); + return response; + } + + @Override + public void onThrowable(Throwable t) { + onThrowableWasNotifiedWithTimeoutException.set(t instanceof TimeoutException); + latch.countDown(); + } + }); + + if (!latch.await(TIMEOUT, SECONDS)) { + fail("Timed out"); + } + + assertFalse(onCompletedWasNotified.get()); + assertTrue(onThrowableWasNotifiedWithTimeoutException.get()); + + try { + whenResponse.get(TIMEOUT, SECONDS); + } catch (ExecutionException e) { + throw e.getCause(); + } + })); + }); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void configRequestTimeoutHappensInDueTime() throws Throwable { + assertThrows(TimeoutException.class, () -> { + withClient(config().setRequestTimeout(Duration.ofSeconds(1))).run(client -> + withServer(server).run(server -> { + HttpHeaders h = new DefaultHttpHeaders(); + h.add(CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + h.add("X-Delay", 2_000); + + server.enqueueEcho(); + long start = unpreciseMillisTime(); + try { + client.prepareGet(getTargetUrl()).setHeaders(h).setUrl(getTargetUrl()).execute().get(); + } catch (Throwable ex) { + final long elapsedTime = unpreciseMillisTime() - start; + assertTrue(elapsedTime >= 1_000 && elapsedTime <= 1_500); + throw ex.getCause(); + } + })); + }); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void getProperPathAndQueryString() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + client.prepareGet(getTargetUrl() + "?foo=bar").execute(new AsyncCompletionHandlerAdapter() { + @Override + public Response onCompleted(Response response) { + assertNotNull(response.getHeader("X-PathInfo")); + assertNotNull(response.getHeader("X-QueryString")); + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void connectionIsReusedForSequentialRequests() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + final CountDownLatch l = new CountDownLatch(2); + + AsyncCompletionHandler handler = new AsyncCompletionHandlerAdapter() { + + volatile String clientPort; + + @Override + public Response onCompleted(Response response) { + try { + assertEquals(response.getStatusCode(), 200); + if (clientPort == null) { + clientPort = response.getHeader("X-ClientPort"); + } else { + // verify that the server saw the same client remote address/port + // so the same connection was used + assertEquals(response.getHeader("X-ClientPort"), clientPort); + } + } finally { + l.countDown(); + } + return response; + } + }; + + server.enqueueEcho(); + client.prepareGet(getTargetUrl()).execute(handler).get(TIMEOUT, SECONDS); + server.enqueueEcho(); + client.prepareGet(getTargetUrl()).execute(handler); + + if (!l.await(TIMEOUT, SECONDS)) { + fail("Timed out"); + } + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void reachingMaxRedirectThrowsMaxRedirectException() throws Throwable { + assertThrows(MaxRedirectException.class, () -> { + withClient(config().setMaxRedirects(1).setFollowRedirect(true)).run(client -> + withServer(server).run(server -> { + try { + // max redirect is 1, so second redirect will fail + server.enqueueRedirect(301, getTargetUrl()); + server.enqueueRedirect(301, getTargetUrl()); + client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerAdapter() { + @Override + public Response onCompleted(Response response) { + fail("Should not be here"); + return response; + } + + @Override + public void onThrowable(Throwable t) { + } + }).get(TIMEOUT, SECONDS); + } catch (ExecutionException e) { + throw e.getCause(); + } + })); + }); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void nonBlockingNestedRequestsFromIoThreadAreFine() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + final int maxNested = 5; + final CountDownLatch latch = new CountDownLatch(2); + + final AsyncCompletionHandlerAdapter handler = new AsyncCompletionHandlerAdapter() { + private final AtomicInteger nestedCount = new AtomicInteger(0); + + @Override + public Response onCompleted(Response response) { + try { + if (nestedCount.getAndIncrement() < maxNested) { + client.prepareGet(getTargetUrl()).execute(this); + } + } finally { + latch.countDown(); + } + return response; + } + }; + + for (int i = 0; i < maxNested + 1; i++) { + server.enqueueOk(); + } + + client.prepareGet(getTargetUrl()).execute(handler); + + if (!latch.await(TIMEOUT, SECONDS)) { + fail("Timed out"); + } + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void optionsIsSupported() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + Response response = client.prepareOptions(getTargetUrl()).execute().get(); + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getHeader("Allow"), "GET,HEAD,POST,OPTIONS,TRACE"); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void cancellingFutureNotifiesOnThrowableWithCancellationException() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + HttpHeaders h = new DefaultHttpHeaders(); + h.add(CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + h.add("X-Delay", 2_000); + + CountDownLatch latch = new CountDownLatch(1); + + Future future = client.preparePost(getTargetUrl()).setHeaders(h).setBody("Body").execute(new AsyncCompletionHandlerAdapter() { + + @Override + public void onThrowable(Throwable t) { + if (t instanceof CancellationException) { + latch.countDown(); + } + } + }); + + future.cancel(true); + if (!latch.await(TIMEOUT, SECONDS)) { + fail("Timed out"); + } + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void getShouldAllowBody() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> + client.prepareGet(getTargetUrl()).setBody("Boo!").execute())); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void malformedUriThrowsException() throws Throwable { + assertThrows(IllegalArgumentException.class, () -> { + withClient().run(client -> + withServer(server).run(server -> client.prepareGet(String.format("http:localhost:%d/foo/test", server.getHttpPort())).build())); + }); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void emptyResponseBodyBytesAreEmpty() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + Response response = client.prepareGet(getTargetUrl()).execute().get(); + assertEquals(response.getStatusCode(), 200); + assertArrayEquals(response.getResponseBodyAsBytes(), ACTUAL); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void newConnectionEventsAreFired() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + + Request request = get(getTargetUrl()).build(); + + EventCollectingHandler handler = new EventCollectingHandler(); + client.executeRequest(request, handler).get(3, SECONDS); + handler.waitForCompletion(3, SECONDS); + + Object[] expectedEvents = { + CONNECTION_POOL_EVENT, + HOSTNAME_RESOLUTION_EVENT, + HOSTNAME_RESOLUTION_SUCCESS_EVENT, + CONNECTION_OPEN_EVENT, + CONNECTION_SUCCESS_EVENT, + REQUEST_SEND_EVENT, + HEADERS_WRITTEN_EVENT, + STATUS_RECEIVED_EVENT, + HEADERS_RECEIVED_EVENT, + CONNECTION_OFFER_EVENT, + COMPLETED_EVENT}; + + assertArrayEquals(handler.firedEvents.toArray(), expectedEvents, "Got " + Arrays.toString(handler.firedEvents.toArray())); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void requestingPlainHttpEndpointOverHttpsThrowsSslException() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + try { + client.prepareGet(getTargetUrl().replace("http", "https")).execute().get(); + fail("Request shouldn't succeed"); + } catch (ExecutionException e) { + assertInstanceOf(ConnectException.class, e.getCause(), "Cause should be a ConnectException"); + assertInstanceOf(SSLException.class, e.getCause().getCause(), "Root cause should be a SslException"); + } + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postUnboundedInputStreamAsBodyStream() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + HttpHeaders h = new DefaultHttpHeaders(); + h.add(CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON); + server.enqueue(new AbstractHandler() { + final EchoHandler chain = new EchoHandler(); + + @Override + public void handle(String target, org.eclipse.jetty.server.Request request, HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse) throws IOException, ServletException { + + assertEquals(request.getHeader(TRANSFER_ENCODING.toString()), HttpHeaderValues.CHUNKED.toString()); + assertNull(request.getHeader(CONTENT_LENGTH.toString())); + chain.handle(target, request, httpServletRequest, httpServletResponse); + } + }); + server.enqueueEcho(); + + client.preparePost(getTargetUrl()) + .setHeaders(h) + .setBody(new ByteArrayInputStream("{}".getBytes(StandardCharsets.ISO_8859_1))) + .execute(new AsyncCompletionHandlerAdapter() { + @Override + public Response onCompleted(Response response) { + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getResponseBody(), "{}"); + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postInputStreamWithContentLengthAsBodyGenerator() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + HttpHeaders h = new DefaultHttpHeaders(); + h.add(CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON); + server.enqueue(new AbstractHandler() { + final EchoHandler chain = new EchoHandler(); + + @Override + public void handle(String target, org.eclipse.jetty.server.Request request, HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse) throws IOException, ServletException { + + assertNull(request.getHeader(TRANSFER_ENCODING.toString())); + assertEquals(request.getHeader(CONTENT_LENGTH.toString()), + Integer.toString("{}".getBytes(StandardCharsets.ISO_8859_1).length)); + chain.handle(target, request, httpServletRequest, httpServletResponse); + } + }); + + byte[] bodyBytes = "{}".getBytes(StandardCharsets.ISO_8859_1); + InputStream bodyStream = new ByteArrayInputStream(bodyBytes); + + client.preparePost(getTargetUrl()) + .setHeaders(h) + .setBody(new InputStreamBodyGenerator(bodyStream, bodyBytes.length)) + .execute(new AsyncCompletionHandlerAdapter() { + + @Override + public Response onCompleted(Response response) { + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getResponseBody(), "{}"); + return response; + } + }).get(TIMEOUT, SECONDS); + })); + } +} diff --git a/client/src/test/java/org/asynchttpclient/BasicHttpsTest.java b/client/src/test/java/org/asynchttpclient/BasicHttpsTest.java new file mode 100644 index 0000000000..f932836b5a --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/BasicHttpsTest.java @@ -0,0 +1,219 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.channel.KeepAliveStrategy; +import org.asynchttpclient.test.EventCollectingHandler; +import org.asynchttpclient.testserver.HttpServer; +import org.asynchttpclient.testserver.HttpTest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Timeout; + +import javax.net.ssl.SSLHandshakeException; +import java.time.Duration; +import java.util.Arrays; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.LARGE_IMAGE_FILE; +import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE; +import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE_STRING; +import static org.asynchttpclient.test.TestUtils.TIMEOUT; +import static org.asynchttpclient.test.TestUtils.createSslEngineFactory; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class BasicHttpsTest extends HttpTest { + + private HttpServer server; + + @BeforeEach + public void start() throws Throwable { + server = new HttpServer(); + server.start(); + } + + @AfterEach + public void stop() throws Throwable { + server.close(); + } + + private String getTargetUrl() { + return server.getHttpsUrl() + "/foo/bar"; + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postFileOverHttps() throws Throwable { + logger.debug(">>> postBodyOverHttps"); + withClient(config().setSslEngineFactory(createSslEngineFactory())).run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + + Response resp = client.preparePost(getTargetUrl()).setBody(SIMPLE_TEXT_FILE).setHeader(CONTENT_TYPE, "text/html").execute().get(); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); + })); + logger.debug("<<< postBodyOverHttps"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postLargeFileOverHttps() throws Throwable { + logger.debug(">>> postLargeFileOverHttps"); + withClient(config().setSslEngineFactory(createSslEngineFactory())).run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + + Response resp = client.preparePost(getTargetUrl()).setBody(LARGE_IMAGE_FILE).setHeader(CONTENT_TYPE, "image/png").execute().get(); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getResponseBodyAsBytes().length, LARGE_IMAGE_FILE.length()); + })); + logger.debug("<<< postLargeFileOverHttps"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void multipleSequentialPostRequestsOverHttps() throws Throwable { + logger.debug(">>> multipleSequentialPostRequestsOverHttps"); + withClient(config().setSslEngineFactory(createSslEngineFactory())).run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + server.enqueueEcho(); + + String body = "hello there"; + Response response = client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute().get(TIMEOUT, SECONDS); + assertEquals(response.getResponseBody(), body); + + response = client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute().get(TIMEOUT, SECONDS); + assertEquals(response.getResponseBody(), body); + })); + logger.debug("<<< multipleSequentialPostRequestsOverHttps"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void multipleConcurrentPostRequestsOverHttpsWithDisabledKeepAliveStrategy() throws Throwable { + logger.debug(">>> multipleConcurrentPostRequestsOverHttpsWithDisabledKeepAliveStrategy"); + + KeepAliveStrategy keepAliveStrategy = (remoteAddress, ahcRequest, nettyRequest, nettyResponse) -> !ahcRequest.getUri().isSecured(); + + withClient(config().setSslEngineFactory(createSslEngineFactory()).setKeepAliveStrategy(keepAliveStrategy)).run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + server.enqueueEcho(); + server.enqueueEcho(); + + String body = "hello there"; + + client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute(); + client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute(); + + Response response = client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute().get(); + assertEquals(response.getResponseBody(), body); + })); + + logger.debug("<<< multipleConcurrentPostRequestsOverHttpsWithDisabledKeepAliveStrategy"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void reconnectAfterFailedCertificationPath() throws Throwable { + logger.debug(">>> reconnectAfterFailedCertificationPath"); + + AtomicBoolean trust = new AtomicBoolean(); + + withClient(config().setMaxRequestRetry(0).setSslEngineFactory(createSslEngineFactory(trust))).run(client -> + withServer(server).run(server -> { + server.enqueueEcho(); + server.enqueueEcho(); + + String body = "hello there"; + + // first request fails because server certificate is rejected + Throwable cause = null; + try { + client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute().get(TIMEOUT, SECONDS); + } catch (final ExecutionException e) { + cause = e.getCause(); + } + assertNotNull(cause); + + // second request should succeed + trust.set(true); + Response response = client.preparePost(getTargetUrl()).setBody(body).setHeader(CONTENT_TYPE, "text/html").execute().get(TIMEOUT, SECONDS); + + assertEquals(response.getResponseBody(), body); + })); + logger.debug("<<< reconnectAfterFailedCertificationPath"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 2000) + public void failInstantlyIfNotAllowedSelfSignedCertificate() throws Throwable { + logger.debug(">>> failInstantlyIfNotAllowedSelfSignedCertificate"); + + assertThrows(SSLHandshakeException.class, () -> { + withClient(config().setMaxRequestRetry(0).setRequestTimeout(Duration.ofSeconds(2))).run(client -> + withServer(server).run(server -> { + try { + client.prepareGet(getTargetUrl()).execute().get(TIMEOUT, SECONDS); + } catch (ExecutionException e) { + throw e.getCause().getCause(); + } + })); + }); + logger.debug("<<< failInstantlyIfNotAllowedSelfSignedCertificate"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testNormalEventsFired() throws Throwable { + logger.debug(">>> testNormalEventsFired"); + + withClient(config().setSslEngineFactory(createSslEngineFactory())).run(client -> + withServer(server).run(server -> { + EventCollectingHandler handler = new EventCollectingHandler(); + + server.enqueueEcho(); + client.preparePost(getTargetUrl()).setBody("whatever").execute(handler).get(3, SECONDS); + handler.waitForCompletion(3, SECONDS); + + Object[] expectedEvents = { + CONNECTION_POOL_EVENT, + HOSTNAME_RESOLUTION_EVENT, + HOSTNAME_RESOLUTION_SUCCESS_EVENT, + CONNECTION_OPEN_EVENT, + CONNECTION_SUCCESS_EVENT, + TLS_HANDSHAKE_EVENT, + TLS_HANDSHAKE_SUCCESS_EVENT, + REQUEST_SEND_EVENT, + HEADERS_WRITTEN_EVENT, + STATUS_RECEIVED_EVENT, + HEADERS_RECEIVED_EVENT, + CONNECTION_OFFER_EVENT, + COMPLETED_EVENT}; + + assertArrayEquals(handler.firedEvents.toArray(), expectedEvents, "Got " + Arrays.toString(handler.firedEvents.toArray())); + })); + logger.debug("<<< testNormalEventsFired"); + } +} diff --git a/client/src/test/java/org/asynchttpclient/ByteBufferCapacityTest.java b/client/src/test/java/org/asynchttpclient/ByteBufferCapacityTest.java new file mode 100644 index 0000000000..a65cd79139 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ByteBufferCapacityTest.java @@ -0,0 +1,98 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.test.TestUtils.createTempFile; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class ByteBufferCapacityTest extends AbstractBasicTest { + + @Override + public AbstractHandler configureHandler() throws Exception { + return new BasicHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicByteBufferTest() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + File largeFile = createTempFile(1024 * 100 * 10); + final AtomicInteger byteReceived = new AtomicInteger(); + + Response response = c.preparePut(getTargetUrl()).setBody(largeFile).execute(new AsyncCompletionHandlerAdapter() { + @Override + public State onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { + byteReceived.addAndGet(content.getBodyByteBuffer().capacity()); + return super.onBodyPartReceived(content); + } + + }).get(); + + assertNotNull(response); + assertEquals(response.getStatusCode(), 200); + assertEquals(byteReceived.get(), largeFile.length()); + assertEquals(response.getResponseBody().length(), largeFile.length()); + } + } + + @Override + public String getTargetUrl() { + return String.format("http://localhost:%d/foo/test", port1); + } + + private static class BasicHandler extends AbstractHandler { + + @Override + public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + Enumeration e = httpRequest.getHeaderNames(); + String param; + while (e.hasMoreElements()) { + param = e.nextElement().toString(); + httpResponse.addHeader("X-" + param, httpRequest.getHeader(param)); + } + + int size = 10 * 1024; + if (httpRequest.getContentLength() > 0) { + size = httpRequest.getContentLength(); + } + byte[] bytes = new byte[size]; + if (bytes.length > 0) { + final InputStream in = httpRequest.getInputStream(); + final OutputStream out = httpResponse.getOutputStream(); + int read; + while ((read = in.read(bytes)) != -1) { + out.write(bytes, 0, read); + } + } + + httpResponse.setStatus(200); + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/ClientStatsTest.java b/client/src/test/java/org/asynchttpclient/ClientStatsTest.java new file mode 100644 index 0000000000..2d8d324de8 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ClientStatsTest.java @@ -0,0 +1,184 @@ +/* + * 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. + * 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 org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Created by grenville on 9/25/16. + */ +public class ClientStatsTest extends AbstractBasicTest { + + private static final String hostname = "localhost"; + + @Test + public void testClientStatus() throws Throwable { + try (final AsyncHttpClient client = asyncHttpClient(config().setKeepAlive(true).setPooledConnectionIdleTimeout(Duration.ofSeconds(5)))) { + final String url = getTargetUrl(); + + final ClientStats emptyStats = client.getClientStats(); + + assertEquals("There are 0 total connections, 0 are active and 0 are idle.", emptyStats.toString()); + assertEquals(0, emptyStats.getTotalActiveConnectionCount()); + assertEquals(0, emptyStats.getTotalIdleConnectionCount()); + assertEquals(0, emptyStats.getTotalConnectionCount()); + assertNull(emptyStats.getStatsPerHost().get(hostname)); + + final List> futures = Stream.generate(() -> client.prepareGet(url).setHeader("LockThread", "6").execute()) + .limit(5) + .collect(Collectors.toList()); + + Thread.sleep(2000); + + final ClientStats activeStats = client.getClientStats(); + + assertEquals("There are 5 total connections, 5 are active and 0 are idle.", activeStats.toString()); + assertEquals(5, activeStats.getTotalActiveConnectionCount()); + assertEquals(0, activeStats.getTotalIdleConnectionCount()); + assertEquals(5, activeStats.getTotalConnectionCount()); + assertEquals(5, activeStats.getStatsPerHost().get(hostname).getHostConnectionCount()); + + futures.forEach(future -> future.toCompletableFuture().join()); + + Thread.sleep(1000); + + final ClientStats idleStats = client.getClientStats(); + + assertEquals("There are 5 total connections, 0 are active and 5 are idle.", idleStats.toString()); + assertEquals(0, idleStats.getTotalActiveConnectionCount()); + assertEquals(5, idleStats.getTotalIdleConnectionCount()); + assertEquals(5, idleStats.getTotalConnectionCount()); + assertEquals(5, idleStats.getStatsPerHost().get(hostname).getHostConnectionCount()); + + // Let's make sure the active count is correct when reusing cached connections. + + final List> repeatedFutures = Stream.generate(() -> client.prepareGet(url).setHeader("LockThread", "6").execute()) + .limit(3) + .collect(Collectors.toList()); + + Thread.sleep(2000); + + final ClientStats activeCachedStats = client.getClientStats(); + + assertEquals("There are 5 total connections, 3 are active and 2 are idle.", activeCachedStats.toString()); + assertEquals(3, activeCachedStats.getTotalActiveConnectionCount()); + assertEquals(2, activeCachedStats.getTotalIdleConnectionCount()); + assertEquals(5, activeCachedStats.getTotalConnectionCount()); + assertEquals(5, activeCachedStats.getStatsPerHost().get(hostname).getHostConnectionCount()); + + repeatedFutures.forEach(future -> future.toCompletableFuture().join()); + + Thread.sleep(1000); + + final ClientStats idleCachedStats = client.getClientStats(); + + assertEquals("There are 3 total connections, 0 are active and 3 are idle.", idleCachedStats.toString()); + assertEquals(0, idleCachedStats.getTotalActiveConnectionCount()); + assertEquals(3, idleCachedStats.getTotalIdleConnectionCount()); + assertEquals(3, idleCachedStats.getTotalConnectionCount()); + assertEquals(3, idleCachedStats.getStatsPerHost().get(hostname).getHostConnectionCount()); + + Thread.sleep(5000); + + final ClientStats timeoutStats = client.getClientStats(); + + assertEquals("There are 0 total connections, 0 are active and 0 are idle.", timeoutStats.toString()); + assertEquals(0, timeoutStats.getTotalActiveConnectionCount()); + assertEquals(0, timeoutStats.getTotalIdleConnectionCount()); + assertEquals(0, timeoutStats.getTotalConnectionCount()); + assertNull(timeoutStats.getStatsPerHost().get(hostname)); + } + } + + @Test + public void testClientStatusNoKeepalive() throws Throwable { + try (final AsyncHttpClient client = asyncHttpClient(config().setKeepAlive(false).setPooledConnectionIdleTimeout(Duration.ofSeconds(1)))) { + final String url = getTargetUrl(); + + final ClientStats emptyStats = client.getClientStats(); + + assertEquals("There are 0 total connections, 0 are active and 0 are idle.", emptyStats.toString()); + assertEquals(0, emptyStats.getTotalActiveConnectionCount()); + assertEquals(0, emptyStats.getTotalIdleConnectionCount()); + assertEquals(0, emptyStats.getTotalConnectionCount()); + assertNull(emptyStats.getStatsPerHost().get(hostname)); + + final List> futures = Stream.generate(() -> client.prepareGet(url).setHeader("LockThread", "6").execute()) + .limit(5) + .collect(Collectors.toList()); + + Thread.sleep(2000); + + final ClientStats activeStats = client.getClientStats(); + + assertEquals("There are 5 total connections, 5 are active and 0 are idle.", activeStats.toString()); + assertEquals(5, activeStats.getTotalActiveConnectionCount()); + assertEquals(0, activeStats.getTotalIdleConnectionCount()); + assertEquals(5, activeStats.getTotalConnectionCount()); + assertEquals(5, activeStats.getStatsPerHost().get(hostname).getHostConnectionCount()); + + futures.forEach(future -> future.toCompletableFuture().join()); + + Thread.sleep(1000); + + final ClientStats idleStats = client.getClientStats(); + + assertEquals("There are 0 total connections, 0 are active and 0 are idle.", idleStats.toString()); + assertEquals(0, idleStats.getTotalActiveConnectionCount()); + assertEquals(0, idleStats.getTotalIdleConnectionCount()); + assertEquals(0, idleStats.getTotalConnectionCount()); + assertNull(idleStats.getStatsPerHost().get(hostname)); + + // Let's make sure the active count is correct when reusing cached connections. + + final List> repeatedFutures = Stream.generate(() -> client.prepareGet(url).setHeader("LockThread", "6").execute()) + .limit(3) + .collect(Collectors.toList()); + + Thread.sleep(2000); + + final ClientStats activeCachedStats = client.getClientStats(); + + assertEquals("There are 3 total connections, 3 are active and 0 are idle.", activeCachedStats.toString()); + assertEquals(3, activeCachedStats.getTotalActiveConnectionCount()); + assertEquals(0, activeCachedStats.getTotalIdleConnectionCount()); + assertEquals(3, activeCachedStats.getTotalConnectionCount()); + assertEquals(3, activeCachedStats.getStatsPerHost().get(hostname).getHostConnectionCount()); + + repeatedFutures.forEach(future -> future.toCompletableFuture().join()); + + Thread.sleep(1000); + + final ClientStats idleCachedStats = client.getClientStats(); + + assertEquals("There are 0 total connections, 0 are active and 0 are idle.", idleCachedStats.toString()); + assertEquals(0, idleCachedStats.getTotalActiveConnectionCount()); + assertEquals(0, idleCachedStats.getTotalIdleConnectionCount()); + assertEquals(0, idleCachedStats.getTotalConnectionCount()); + assertNull(idleCachedStats.getStatsPerHost().get(hostname)); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/ComplexClientTest.java b/client/src/test/java/org/asynchttpclient/ComplexClientTest.java new file mode 100644 index 0000000000..089be3d6ad --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ComplexClientTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; + +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ComplexClientTest extends AbstractBasicTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void multipleRequestsTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + String body = "hello there"; + + // once + Response response = client.preparePost(getTargetUrl()) + .setBody(body) + .setHeader("Content-Type", "text/html") + .execute() + .get(TIMEOUT, TimeUnit.SECONDS); + + assertEquals(response.getResponseBody(), body); + + // twice + response = client.preparePost(getTargetUrl()) + .setBody(body) + .setHeader("Content-Type", "text/html") + .execute() + .get(TIMEOUT, TimeUnit.SECONDS); + + assertEquals(body, response.getResponseBody()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void urlWithoutSlashTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + String body = "hello there"; + Response response = client.preparePost(String.format("http://localhost:%d/foo/test", port1)) + .setBody(body) + .setHeader("Content-Type", "text/html") + .execute() + .get(TIMEOUT, TimeUnit.SECONDS); + + assertEquals(body, response.getResponseBody()); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/CookieStoreTest.java b/client/src/test/java/org/asynchttpclient/CookieStoreTest.java new file mode 100644 index 0000000000..fd65322c14 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/CookieStoreTest.java @@ -0,0 +1,371 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.cookie.ClientCookieDecoder; +import io.netty.handler.codec.http.cookie.ClientCookieEncoder; +import io.netty.handler.codec.http.cookie.Cookie; +import io.netty.handler.codec.http.cookie.DefaultCookie; +import org.asynchttpclient.cookie.CookieStore; +import org.asynchttpclient.cookie.ThreadSafeCookieStore; +import org.asynchttpclient.uri.Uri; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CookieStoreTest { + + private static final Logger logger = LoggerFactory.getLogger(CookieStoreTest.class); + + @BeforeEach + public void setUpGlobal() { + logger.info("Local HTTP server started successfully"); + System.out.println("--Start"); + } + + @AfterEach + public void tearDownGlobal() { + System.out.println("--Stop"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void runAllSequentiallyBecauseNotThreadSafe() throws Exception { + addCookieWithEmptyPath(); + dontReturnCookieForAnotherDomain(); + returnCookieWhenItWasSetOnSamePath(); + returnCookieWhenItWasSetOnParentPath(); + dontReturnCookieWhenDomainMatchesButPathIsDifferent(); + dontReturnCookieWhenDomainMatchesButPathIsParent(); + returnCookieWhenDomainMatchesAndPathIsChild(); + returnCookieWhenItWasSetOnSubdomain(); + replaceCookieWhenSetOnSameDomainAndPath(); + dontReplaceCookiesWhenTheyHaveDifferentName(); + expireCookieWhenSetWithDateInThePast(); + cookieWithSameNameMustCoexistIfSetOnDifferentDomains(); + handleMissingDomainAsRequestHost(); + handleMissingPathAsSlash(); + returnTheCookieWheniTSissuedFromRequestWithSubpath(); + handleMissingPathAsRequestPathWhenFromRootDir(); + handleMissingPathAsRequestPathWhenPathIsNotEmpty(); + handleDomainInCaseInsensitiveManner(); + handleCookieNameInCaseInsensitiveManner(); + handleCookiePathInCaseSensitiveManner(); + ignoreQueryParametersInUri(); + shouldServerOnSubdomainWhenDomainMatches(); + replaceCookieWhenSetOnSamePathBySameUri(); + handleMultipleCookieOfSameNameOnDifferentPaths(); + handleTrailingSlashesInPaths(); + returnMultipleCookiesEvenIfTheyHaveSameName(); + shouldServeCookiesBasedOnTheUriScheme(); + shouldAlsoServeNonSecureCookiesBasedOnTheUriScheme(); + shouldNotServeSecureCookiesForDefaultRetrievedHttpUriScheme(); + shouldServeSecureCookiesForSpecificallyRetrievedHttpUriScheme(); + shouldCleanExpiredCookieFromUnderlyingDataStructure(); + } + + private static void addCookieWithEmptyPath() { + CookieStore store = new ThreadSafeCookieStore(); + Uri uri = Uri.create("http://www.foo.com/"); + store.add(uri, ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; path=")); + assertFalse(store.get(uri).isEmpty()); + } + + private static void dontReturnCookieForAnotherDomain() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://www.foo.com/"), ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; path=")); + assertTrue(store.get(Uri.create("http://www.bar.com/")).isEmpty()); + } + + private static void returnCookieWhenItWasSetOnSamePath() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://www.foo.com/"), ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; path=/bar/")); + assertEquals(1, store.get(Uri.create("http://www.foo.com/bar/")).size()); + } + + private static void returnCookieWhenItWasSetOnParentPath() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://www.foo.com/"), ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; Domain=www.foo.com; path=/bar")); + assertEquals(1, store.get(Uri.create("http://www.foo.com/bar/baz")).size()); + } + + private static void dontReturnCookieWhenDomainMatchesButPathIsDifferent() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://www.foo.com/bar"), ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; Domain=www.foo.com; path=/bar")); + assertTrue(store.get(Uri.create("http://www.foo.com/baz")).isEmpty()); + } + + private static void dontReturnCookieWhenDomainMatchesButPathIsParent() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://www.foo.com/bar"), ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; Domain=www.foo.com; path=/bar")); + assertTrue(store.get(Uri.create("http://www.foo.com/")).isEmpty()); + } + + private static void returnCookieWhenDomainMatchesAndPathIsChild() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://www.foo.com/bar"), ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; Domain=www.foo.com; path=/bar")); + assertEquals(1, store.get(Uri.create("http://www.foo.com/bar/baz")).size()); + } + + private static void returnCookieWhenItWasSetOnSubdomain() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://www.foo.com/"), ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; Domain=.foo.com")); + assertEquals(1, store.get(Uri.create("http://bar.foo.com/")).size()); + } + + private static void replaceCookieWhenSetOnSameDomainAndPath() { + CookieStore store = new ThreadSafeCookieStore(); + Uri uri = Uri.create("http://www.foo.com/bar/baz"); + store.add(uri, ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; Domain=www.foo.com; path=/bar")); + store.add(uri, ClientCookieDecoder.LAX.decode("ALPHA=VALUE2; Domain=www.foo.com; path=/bar")); + assertEquals(1, store.getAll().size()); + assertEquals("VALUE2", store.get(uri).get(0).value()); + } + + private static void dontReplaceCookiesWhenTheyHaveDifferentName() { + CookieStore store = new ThreadSafeCookieStore(); + Uri uri = Uri.create("http://www.foo.com/bar/baz"); + store.add(uri, ClientCookieDecoder.LAX.decode("BETA=VALUE1; Domain=www.foo.com; path=/bar")); + store.add(uri, ClientCookieDecoder.LAX.decode("ALPHA=VALUE2; Domain=www.foo.com; path=/bar")); + assertEquals(2, store.get(uri).size()); + } + + private static void expireCookieWhenSetWithDateInThePast() { + CookieStore store = new ThreadSafeCookieStore(); + Uri uri = Uri.create("http://www.foo.com/bar"); + store.add(uri, ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; Domain=www.foo.com; path=/bar")); + store.add(uri, ClientCookieDecoder.LAX.decode("ALPHA=EXPIRED; Domain=www.foo.com; Path=/bar; Expires=Sun, 06 Nov 1994 08:49:37 GMT")); + assertTrue(store.getAll().isEmpty()); + } + + private static void cookieWithSameNameMustCoexistIfSetOnDifferentDomains() { + CookieStore store = new ThreadSafeCookieStore(); + Uri uri1 = Uri.create("http://www.foo.com/"); + store.add(uri1, ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; Domain=www.foo.com")); + Uri uri2 = Uri.create("http://www.bar.com/"); + store.add(uri2, ClientCookieDecoder.LAX.decode("ALPHA=VALUE2; Domain=www.bar.com")); + + assertEquals(1, store.get(uri1).size()); + assertEquals("VALUE1", store.get(uri1).get(0).value()); + + assertEquals(1, store.get(uri2).size()); + assertEquals("VALUE2", store.get(uri2).get(0).value()); + } + + private static void handleMissingDomainAsRequestHost() { + CookieStore store = new ThreadSafeCookieStore(); + Uri uri = Uri.create("http://www.foo.com/"); + store.add(uri, ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; Path=/")); + assertEquals(1, store.get(uri).size()); + } + + private static void handleMissingPathAsSlash() { + CookieStore store = new ThreadSafeCookieStore(); + Uri uri = Uri.create("http://www.foo.com/"); + store.add(uri, ClientCookieDecoder.LAX.decode("tooe_token=0b1d81dd02d207491a6e9b0a2af9470da9eb1dad")); + assertEquals(1, store.get(uri).size()); + } + + private static void returnTheCookieWheniTSissuedFromRequestWithSubpath() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://www.foo.com/bar"), ClientCookieDecoder.LAX.decode("ALPHA=VALUE; path=/")); + assertEquals(1, store.get(Uri.create("http://www.foo.com/")).size()); + } + + private static void handleMissingPathAsRequestPathWhenFromRootDir() { + CookieStore store = new ThreadSafeCookieStore(); + Uri uri = Uri.create("http://www.foo.com/"); + store.add(uri, ClientCookieDecoder.LAX.decode("ALPHA=VALUE1")); + assertEquals(1, store.get(uri).size()); + } + + private static void handleMissingPathAsRequestPathWhenPathIsNotEmpty() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://www.foo.com/bar"), ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; Domain=www.foo.com; path=/bar")); + assertTrue(store.get(Uri.create("http://www.foo.com/baz")).isEmpty()); + } + + // RFC 2965 sec. 3.3.3 + private static void handleDomainInCaseInsensitiveManner() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://www.foo.com/bar"), ClientCookieDecoder.LAX.decode("ALPHA=VALUE1")); + assertEquals(1, store.get(Uri.create("http://www.foo.com/bar")).size()); + } + + // RFC 2965 sec. 3.3.3 + private static void handleCookieNameInCaseInsensitiveManner() { + CookieStore store = new ThreadSafeCookieStore(); + Uri uri = Uri.create("http://www.foo.com/bar/baz"); + store.add(uri, ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; Domain=www.foo.com; path=/bar")); + store.add(uri, ClientCookieDecoder.LAX.decode("alpha=VALUE2; Domain=www.foo.com; path=/bar")); + assertEquals(1, store.getAll().size()); + assertEquals("VALUE2", store.get(uri).get(0).value()); + } + + // RFC 2965 sec. 3.3.3 + private static void handleCookiePathInCaseSensitiveManner() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://www.foo.com/foo/bar"), ClientCookieDecoder.LAX.decode("ALPHA=VALUE1")); + assertTrue(store.get(Uri.create("http://www.foo.com/Foo/bAr")).isEmpty()); + } + + private static void ignoreQueryParametersInUri() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://www.foo.com/bar?query1"), ClientCookieDecoder.LAX.decode("ALPHA=VALUE1; Domain=www.foo.com; path=/")); + assertEquals(1, store.get(Uri.create("http://www.foo.com/bar?query2")).size()); + } + + // RFC 6265, 5.1.3. Domain Matching + private static void shouldServerOnSubdomainWhenDomainMatches() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("https://x.foo.org/"), ClientCookieDecoder.LAX.decode("cookie1=VALUE1; Path=/; Domain=foo.org;")); + assertEquals(1, store.get(Uri.create("https://y.x.foo.org/")).size()); + } + + // NOTE: Similar to replaceCookieWhenSetOnSameDomainAndPath() + private static void replaceCookieWhenSetOnSamePathBySameUri() { + CookieStore store = new ThreadSafeCookieStore(); + Uri uri = Uri.create("https://foo.org/"); + store.add(uri, ClientCookieDecoder.LAX.decode("cookie1=VALUE1; Path=/")); + store.add(uri, ClientCookieDecoder.LAX.decode("cookie1=VALUE2; Path=/")); + store.add(uri, ClientCookieDecoder.LAX.decode("cookie1=VALUE3; Path=/")); + assertEquals(1, store.getAll().size()); + assertEquals("VALUE3", store.get(uri).get(0).value()); + } + + private static void handleMultipleCookieOfSameNameOnDifferentPaths() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://www.foo.com/"), ClientCookieDecoder.LAX.decode("cookie=VALUE0; path=/")); + store.add(Uri.create("http://www.foo.com/foo/bar"), ClientCookieDecoder.LAX.decode("cookie=VALUE1; path=/foo/bar/")); + store.add(Uri.create("http://www.foo.com/foo/baz"), ClientCookieDecoder.LAX.decode("cookie=VALUE2; path=/foo/baz/")); + + Uri uri1 = Uri.create("http://www.foo.com/foo/bar/"); + List cookies1 = store.get(uri1); + assertEquals(2, cookies1.size()); + assertEquals(2, cookies1.stream().filter(c -> "VALUE0".equals(c.value()) || "VALUE1".equals(c.value())).count()); + + Uri uri2 = Uri.create("http://www.foo.com/foo/baz/"); + List cookies2 = store.get(uri2); + assertEquals(2, cookies2.size()); + assertEquals(2, cookies2.stream().filter(c -> "VALUE0".equals(c.value()) || "VALUE2".equals(c.value())).count()); + } + + private static void handleTrailingSlashesInPaths() { + CookieStore store = new ThreadSafeCookieStore(); + store.add( + Uri.create("https://vagrant.moolb.com/app/consumer/j_spring_cas_security_check?ticket=ST-5-Q7gzqPpvG3N3Bb02bm3q-llinder-vagrantmgr.moolb.com"), + ClientCookieDecoder.LAX.decode("JSESSIONID=211D17F016132BCBD31D9ABB31D90960; Path=/app/consumer/; HttpOnly")); + assertEquals(1, store.getAll().size()); + assertEquals("211D17F016132BCBD31D9ABB31D90960", store.get(Uri.create("https://vagrant.moolb.com/app/consumer/")).get(0).value()); + } + + private static void returnMultipleCookiesEvenIfTheyHaveSameName() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("http://foo.com/"), ClientCookieDecoder.LAX.decode("JSESSIONID=FOO; Domain=.foo.com")); + store.add(Uri.create("http://sub.foo.com/"), ClientCookieDecoder.LAX.decode("JSESSIONID=BAR; Domain=sub.foo.com")); + + Uri uri1 = Uri.create("http://sub.foo.com/"); + List cookies1 = store.get(uri1); + assertEquals(2, cookies1.size()); + assertEquals(2, cookies1.stream().filter(c -> "FOO".equals(c.value()) || "BAR".equals(c.value())).count()); + + List encodedCookieStrings = cookies1.stream().map(ClientCookieEncoder.LAX::encode).collect(Collectors.toList()); + assertTrue(encodedCookieStrings.contains("JSESSIONID=FOO")); + assertTrue(encodedCookieStrings.contains("JSESSIONID=BAR")); + } + + // rfc6265#section-1 Cookies for a given host are shared across all the ports on that host + private static void shouldServeCookiesBasedOnTheUriScheme() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("https://foo.org/moodle/"), ClientCookieDecoder.LAX.decode("cookie1=VALUE1; Path=/")); + store.add(Uri.create("https://foo.org/moodle/login"), ClientCookieDecoder.LAX.decode("cookie1=VALUE2; Path=/")); + store.add(Uri.create("https://foo.org/moodle/login"), ClientCookieDecoder.LAX.decode("cookie1=VALUE3; Path=/; Secure")); + + Uri uri = Uri.create("https://foo.org/moodle/login"); + assertEquals(1, store.getAll().size()); + assertEquals("VALUE3", store.get(uri).get(0).value()); + assertTrue(store.get(uri).get(0).isSecure()); + } + + // rfc6265#section-1 Cookies for a given host are shared across all the ports on that host + private static void shouldAlsoServeNonSecureCookiesBasedOnTheUriScheme() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("https://foo.org/moodle/"), ClientCookieDecoder.LAX.decode("cookie1=VALUE1; Path=/")); + store.add(Uri.create("https://foo.org/moodle/login"), ClientCookieDecoder.LAX.decode("cookie1=VALUE2; Path=/")); + store.add(Uri.create("https://foo.org/moodle/login"), ClientCookieDecoder.LAX.decode("cookie1=VALUE3; Path=/; HttpOnly")); + + Uri uri = Uri.create("https://foo.org/moodle/login"); + assertEquals(1, store.getAll().size()); + assertEquals("VALUE3", store.get(uri).get(0).value()); + assertFalse(store.get(uri).get(0).isSecure()); + } + + // rfc6265#section-1 Cookies for a given host are shared across all the ports on that host + private static void shouldNotServeSecureCookiesForDefaultRetrievedHttpUriScheme() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("https://foo.org/moodle/"), ClientCookieDecoder.LAX.decode("cookie1=VALUE1; Path=/")); + store.add(Uri.create("https://foo.org/moodle/login"), ClientCookieDecoder.LAX.decode("cookie1=VALUE2; Path=/")); + store.add(Uri.create("https://foo.org/moodle/login"), ClientCookieDecoder.LAX.decode("cookie1=VALUE3; Path=/; Secure")); + + Uri uri = Uri.create("http://foo.org/moodle/login"); + assertTrue(store.get(uri).isEmpty()); + } + + // rfc6265#section-1 Cookies for a given host are shared across all the ports on that host + private static void shouldServeSecureCookiesForSpecificallyRetrievedHttpUriScheme() { + CookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("https://foo.org/moodle/"), ClientCookieDecoder.LAX.decode("cookie1=VALUE1; Path=/")); + store.add(Uri.create("https://foo.org/moodle/login"), ClientCookieDecoder.LAX.decode("cookie1=VALUE2; Path=/")); + store.add(Uri.create("https://foo.org/moodle/login"), ClientCookieDecoder.LAX.decode("cookie1=VALUE3; Path=/; Secure")); + + Uri uri = Uri.create("https://foo.org/moodle/login"); + assertEquals(1, store.get(uri).size()); + assertEquals("VALUE3", store.get(uri).get(0).value()); + assertTrue(store.get(uri).get(0).isSecure()); + } + + private static void shouldCleanExpiredCookieFromUnderlyingDataStructure() throws Exception { + ThreadSafeCookieStore store = new ThreadSafeCookieStore(); + store.add(Uri.create("https://foo.org/moodle/"), getCookie("JSESSIONID", "FOO", 1)); + store.add(Uri.create("https://bar.org/moodle/"), getCookie("JSESSIONID", "BAR", 1)); + store.add(Uri.create("https://bar.org/moodle/"), new DefaultCookie("UNEXPIRED_BAR", "BAR")); + store.add(Uri.create("https://foobar.org/moodle/"), new DefaultCookie("UNEXPIRED_FOOBAR", "FOOBAR")); + + + assertEquals(4, store.getAll().size()); + Thread.sleep(2000); + store.evictExpired(); + assertEquals(2, store.getUnderlying().size()); + Collection unexpiredCookieNames = store.getAll().stream().map(Cookie::name).collect(Collectors.toList()); + assertTrue(unexpiredCookieNames.containsAll(Set.of("UNEXPIRED_BAR", "UNEXPIRED_FOOBAR"))); + } + + private static Cookie getCookie(String key, String value, int maxAge) { + DefaultCookie cookie = new DefaultCookie(key, value); + cookie.setMaxAge(maxAge); + return cookie; + } +} diff --git a/client/src/test/java/org/asynchttpclient/CustomRemoteAddressTest.java b/client/src/test/java/org/asynchttpclient/CustomRemoteAddressTest.java new file mode 100755 index 0000000000..437446f388 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/CustomRemoteAddressTest.java @@ -0,0 +1,57 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.util.internal.SocketUtils; +import org.asynchttpclient.test.TestUtils.AsyncCompletionHandlerAdapter; +import org.asynchttpclient.testserver.HttpServer; +import org.asynchttpclient.testserver.HttpTest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.asynchttpclient.Dsl.get; +import static org.asynchttpclient.test.TestUtils.TIMEOUT; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CustomRemoteAddressTest extends HttpTest { + + private HttpServer server; + + @BeforeEach + public void start() throws Throwable { + server = new HttpServer(); + server.start(); + } + + @AfterEach + public void stop() throws Throwable { + server.close(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void getRootUrlWithCustomRemoteAddress() throws Throwable { + withClient().run(client -> + withServer(server).run(server -> { + String url = server.getHttpUrl(); + server.enqueueOk(); + RequestBuilder request = get(url).setAddress(SocketUtils.addressByName("localhost")); + Response response = client.executeRequest(request, new AsyncCompletionHandlerAdapter()).get(TIMEOUT, SECONDS); + assertEquals(response.getStatusCode(), 200); + })); + } +} 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 0000000000..1548d6812f --- /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 new file mode 100644 index 0000000000..fc7a1c2db4 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/DefaultAsyncHttpClientTest.java @@ -0,0 +1,174 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.kqueue.KQueueEventLoopGroup; +import io.netty.incubator.channel.uring.IOUringEventLoopGroup; +import io.netty.util.Timer; +import org.asynchttpclient.cookie.CookieEvictionTask; +import org.asynchttpclient.cookie.CookieStore; +import org.asynchttpclient.cookie.ThreadSafeCookieStore; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class DefaultAsyncHttpClientTest { + + @RepeatedIfExceptionsTest(repeats = 5) + @EnabledOnOs(OS.LINUX) + public void testNativeTransportWithEpollOnly() throws Exception { + AsyncHttpClientConfig config = config().setUseNativeTransport(true).setUseOnlyEpollNativeTransport(true).build(); + + try (DefaultAsyncHttpClient client = (DefaultAsyncHttpClient) asyncHttpClient(config)) { + assertDoesNotThrow(() -> client.prepareGet("https://www.google.com/").execute().get()); + assertInstanceOf(EpollEventLoopGroup.class, client.channelManager().getEventLoopGroup()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @EnabledOnOs(OS.LINUX) + public void testNativeTransportWithoutEpollOnly() throws Exception { + AsyncHttpClientConfig config = config().setUseNativeTransport(true).setUseOnlyEpollNativeTransport(false).build(); + try (DefaultAsyncHttpClient client = (DefaultAsyncHttpClient) asyncHttpClient(config)) { + assertDoesNotThrow(() -> client.prepareGet("https://www.google.com/").execute().get()); + assertInstanceOf(IOUringEventLoopGroup.class, client.channelManager().getEventLoopGroup()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @EnabledOnOs(OS.MAC) + public void testNativeTransportKQueueOnMacOs() throws Exception { + AsyncHttpClientConfig config = config().setUseNativeTransport(true).build(); + try (DefaultAsyncHttpClient client = (DefaultAsyncHttpClient) asyncHttpClient(config)) { + assertDoesNotThrow(() -> client.prepareGet("https://www.google.com/").execute().get()); + assertInstanceOf(KQueueEventLoopGroup.class, client.channelManager().getEventLoopGroup()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testUseOnlyEpollNativeTransportButNativeTransportIsDisabled() { + assertThrows(IllegalArgumentException.class, () -> config().setUseNativeTransport(false).setUseOnlyEpollNativeTransport(true).build()); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testUseOnlyEpollNativeTransportAndNativeTransportIsEnabled() { + assertDoesNotThrow(() -> config().setUseNativeTransport(true).setUseOnlyEpollNativeTransport(true).build()); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testWithSharedNettyTimerShouldScheduleCookieEvictionOnlyOnce() throws IOException { + Timer nettyTimerMock = mock(Timer.class); + CookieStore cookieStore = new ThreadSafeCookieStore(); + AsyncHttpClientConfig config = config().setNettyTimer(nettyTimerMock).setCookieStore(cookieStore).build(); + + try (AsyncHttpClient client1 = asyncHttpClient(config)) { + try (AsyncHttpClient client2 = asyncHttpClient(config)) { + assertEquals(cookieStore.count(), 2); + verify(nettyTimerMock, times(1)).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class)); + } + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testWitDefaultConfigShouldScheduleCookieEvictionForEachAHC() throws IOException { + AsyncHttpClientConfig config1 = config().build(); + try (AsyncHttpClient client1 = asyncHttpClient(config1)) { + AsyncHttpClientConfig config2 = config().build(); + try (AsyncHttpClient client2 = asyncHttpClient(config2)) { + assertEquals(config1.getCookieStore().count(), 1); + assertEquals(config2.getCookieStore().count(), 1); + } + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testWithSharedCookieStoreButNonSharedTimerShouldScheduleCookieEvictionForFirstAHC() throws IOException { + CookieStore cookieStore = new ThreadSafeCookieStore(); + Timer nettyTimerMock1 = mock(Timer.class); + AsyncHttpClientConfig config1 = config() + .setCookieStore(cookieStore).setNettyTimer(nettyTimerMock1).build(); + + try (AsyncHttpClient client1 = asyncHttpClient(config1)) { + Timer nettyTimerMock2 = mock(Timer.class); + AsyncHttpClientConfig config2 = config() + .setCookieStore(cookieStore).setNettyTimer(nettyTimerMock2).build(); + try (AsyncHttpClient client2 = asyncHttpClient(config2)) { + assertEquals(config1.getCookieStore().count(), 2); + verify(nettyTimerMock1, times(1)).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class)); + verify(nettyTimerMock2, never()).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class)); + } + } + + Timer nettyTimerMock3 = mock(Timer.class); + AsyncHttpClientConfig config3 = config() + .setCookieStore(cookieStore).setNettyTimer(nettyTimerMock3).build(); + + try (AsyncHttpClient client2 = asyncHttpClient(config3)) { + assertEquals(config1.getCookieStore().count(), 1); + verify(nettyTimerMock3, times(1)).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class)); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testWithSharedCookieStoreButNonSharedTimerShouldReScheduleCookieEvictionWhenFirstInstanceGetClosed() throws IOException { + CookieStore cookieStore = new ThreadSafeCookieStore(); + Timer nettyTimerMock1 = mock(Timer.class); + AsyncHttpClientConfig config1 = config() + .setCookieStore(cookieStore).setNettyTimer(nettyTimerMock1).build(); + + try (AsyncHttpClient client1 = asyncHttpClient(config1)) { + assertEquals(config1.getCookieStore().count(), 1); + verify(nettyTimerMock1, times(1)).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class)); + } + + assertEquals(config1.getCookieStore().count(), 0); + + Timer nettyTimerMock2 = mock(Timer.class); + AsyncHttpClientConfig config2 = config() + .setCookieStore(cookieStore).setNettyTimer(nettyTimerMock2).build(); + + try (AsyncHttpClient client2 = asyncHttpClient(config2)) { + assertEquals(config1.getCookieStore().count(), 1); + verify(nettyTimerMock2, times(1)).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class)); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDisablingCookieStore() throws IOException { + AsyncHttpClientConfig config = config() + .setCookieStore(null).build(); + try (AsyncHttpClient client = asyncHttpClient(config)) { + // + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/DigestAuthTest.java b/client/src/test/java/org/asynchttpclient/DigestAuthTest.java new file mode 100644 index 0000000000..d847396bd3 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/DigestAuthTest.java @@ -0,0 +1,104 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +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; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.digestAuthRealm; +import static org.asynchttpclient.test.TestUtils.ADMIN; +import static org.asynchttpclient.test.TestUtils.USER; +import static org.asynchttpclient.test.TestUtils.addDigestAuthHandler; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class DigestAuthTest extends AbstractBasicTest { + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector = addHttpConnector(server); + addDigestAuthHandler(server, configureHandler()); + server.start(); + port1 = connector.getLocalPort(); + logger.info("Local HTTP server started successfully"); + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new SimpleHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void digestAuthTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.prepareGet("http://localhost/" + port1 + '/') + .setRealm(digestAuthRealm(USER, ADMIN).setRealmName("MyRealm").build()) + .execute(); + Response resp = f.get(60, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertNotNull(resp.getHeader("X-Auth")); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void digestAuthTestWithoutScheme() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.prepareGet("http://localhost/" + port1 + '/') + .setRealm(digestAuthRealm(USER, ADMIN).setRealmName("MyRealm").build()) + .execute(); + Response resp = f.get(60, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertNotNull(resp.getHeader("X-Auth")); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void digestAuthNegativeTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.prepareGet("http://localhost/" + port1 + '/') + .setRealm(digestAuthRealm("fake", ADMIN).build()) + .execute(); + Response resp = f.get(20, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), 401); + } + } + + private static class SimpleHandler extends AbstractHandler { + + @Override + public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + response.addHeader("X-Auth", request.getHeader("Authorization")); + response.setStatus(200); + response.getOutputStream().flush(); + response.getOutputStream().close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/EofTerminatedTest.java b/client/src/test/java/org/asynchttpclient/EofTerminatedTest.java new file mode 100644 index 0000000000..b63412df5f --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/EofTerminatedTest.java @@ -0,0 +1,62 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.HttpHeaderValues; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.gzip.GzipHandler; + +import java.io.IOException; + +import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT_ENCODING; +import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; + +public class EofTerminatedTest extends AbstractBasicTest { + + @Override + protected String getTargetUrl() { + return String.format("http://localhost:%d/", port1); + } + + @Override + public AbstractHandler configureHandler() throws Exception { + GzipHandler gzipHandler = new GzipHandler(); + gzipHandler.setHandler(new StreamHandler()); + return gzipHandler; + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testEolTerminatedResponse() throws Exception { + try (AsyncHttpClient ahc = asyncHttpClient(config().setMaxRequestRetry(0))) { + ahc.executeRequest(ahc.prepareGet(getTargetUrl()).setHeader(ACCEPT_ENCODING, HttpHeaderValues.GZIP_DEFLATE).setHeader(CONNECTION, HttpHeaderValues.CLOSE).build()) + .get(); + } + } + + private static class StreamHandler extends AbstractHandler { + @Override + public void handle(String pathInContext, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + request.getResponse().getHttpOutput().sendContent(EofTerminatedTest.class.getClassLoader().getResourceAsStream("SimpleTextFile.txt")); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/ErrorResponseTest.java b/client/src/test/java/org/asynchttpclient/ErrorResponseTest.java new file mode 100644 index 0000000000..59b6a07c0a --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ErrorResponseTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Tests to reproduce issues with handling of error responses + * + * @author Tatu Saloranta + */ +public class ErrorResponseTest extends AbstractBasicTest { + static final String BAD_REQUEST_STR = "Very Bad Request! No cookies."; + + @Override + public AbstractHandler configureHandler() throws Exception { + return new ErrorHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testQueryParameters() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.prepareGet("http://localhost/" + port1 + "/foo").addHeader("Accepts", "*/*").execute(); + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), 400); + assertEquals(resp.getResponseBody(), BAD_REQUEST_STR); + } + } + + private static class ErrorHandler extends AbstractHandler { + + @Override + public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + try { + Thread.sleep(210L); + } catch (InterruptedException e) { + // + } + response.setContentType("text/plain"); + response.setStatus(400); + OutputStream out = response.getOutputStream(); + out.write(BAD_REQUEST_STR.getBytes(UTF_8)); + out.flush(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/Expect100ContinueTest.java b/client/src/test/java/org/asynchttpclient/Expect100ContinueTest.java new file mode 100644 index 0000000000..f604feeeb8 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/Expect100ContinueTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.HttpHeaderValues; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.util.concurrent.Future; + +import static io.netty.handler.codec.http.HttpHeaderNames.EXPECT; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE; +import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE_STRING; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Test the Expect: 100-Continue. + */ +public class Expect100ContinueTest extends AbstractBasicTest { + + @Override + public AbstractHandler configureHandler() throws Exception { + return new ZeroCopyHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void Expect100Continue() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.preparePut("http://localhost/" + port1 + '/') + .setHeader(EXPECT, HttpHeaderValues.CONTINUE) + .setBody(SIMPLE_TEXT_FILE) + .execute(); + Response resp = f.get(); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); + } + } + + private static class ZeroCopyHandler extends AbstractHandler { + + @Override + public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + + int size = 10 * 1024; + if (httpRequest.getContentLength() > 0) { + size = httpRequest.getContentLength(); + } + byte[] bytes = new byte[size]; + if (bytes.length > 0) { + final int read = httpRequest.getInputStream().read(bytes); + httpResponse.getOutputStream().write(bytes, 0, read); + } + + httpResponse.setStatus(200); + httpResponse.getOutputStream().flush(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/FollowingThreadTest.java b/client/src/test/java/org/asynchttpclient/FollowingThreadTest.java new file mode 100644 index 0000000000..444e13c285 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/FollowingThreadTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.HttpHeaders; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; + +/** + * Simple stress test for exercising the follow redirect. + */ +public class FollowingThreadTest extends AbstractBasicTest { + + private static final int COUNT = 10; + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 30 * 1000) + public void testFollowRedirect() throws InterruptedException { + + final CountDownLatch countDown = new CountDownLatch(COUNT); + ExecutorService pool = Executors.newCachedThreadPool(); + try { + for (int i = 0; i < COUNT; i++) { + pool.submit(new Runnable() { + + private int status; + + @Override + public void run() { + final CountDownLatch l = new CountDownLatch(1); + try (AsyncHttpClient ahc = asyncHttpClient(config().setFollowRedirect(true))) { + ahc.prepareGet("http://www.google.com/").execute(new AsyncHandler() { + + @Override + public void onThrowable(Throwable t) { + t.printStackTrace(); + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart bodyPart) { + System.out.println(new String(bodyPart.getBodyPartBytes())); + return State.CONTINUE; + } + + @Override + public State onStatusReceived(HttpResponseStatus responseStatus) { + status = responseStatus.getStatusCode(); + System.out.println(responseStatus.getStatusText()); + return State.CONTINUE; + } + + @Override + public State onHeadersReceived(HttpHeaders headers) { + return State.CONTINUE; + } + + @Override + public Integer onCompleted() { + l.countDown(); + return status; + } + }); + + l.await(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + countDown.countDown(); + } + } + }); + } + countDown.await(); + } finally { + pool.shutdown(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/Head302Test.java b/client/src/test/java/org/asynchttpclient/Head302Test.java new file mode 100644 index 0000000000..7a81dad762 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/Head302Test.java @@ -0,0 +1,97 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.head; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Tests HEAD request that gets 302 response. + * + * @author Hubert Iwaniuk + */ +public class Head302Test extends AbstractBasicTest { + + @Override + public AbstractHandler configureHandler() throws Exception { + return new Head302handler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testHEAD302() throws Exception { + AsyncHttpClientConfig clientConfig = new DefaultAsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); + try (AsyncHttpClient client = asyncHttpClient(clientConfig)) { + final CountDownLatch l = new CountDownLatch(1); + Request request = head("http://localhost/" + port1 + "/Test").build(); + + Response response = client.executeRequest(request, new AsyncCompletionHandlerBase() { + @Override + public Response onCompleted(Response response) throws Exception { + l.countDown(); + return super.onCompleted(response); + } + }).get(3, TimeUnit.SECONDS); + + if (l.await(TIMEOUT, TimeUnit.SECONDS)) { + assertEquals(response.getStatusCode(), HttpServletResponse.SC_OK); + System.out.println(response.getResponseBody()); + // TODO: 19-11-2022 PTAL +// assertTrue(response.getResponseBody().endsWith("_moved")); + } else { + fail("Timeout out"); + } + } + } + + /** + * Handler that does Found (302) in response to HEAD method. + */ + private static class Head302handler extends AbstractHandler { + @Override + public void handle(String s, org.eclipse.jetty.server.Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + if ("HEAD".equalsIgnoreCase(request.getMethod())) { + // See https://github.com/AsyncHttpClient/async-http-client/issues/1728#issuecomment-700007980 + // When setFollowRedirect == TRUE, a follow-up request to a HEAD request will also be a HEAD. + // This will cause an infinite loop, which will error out once the maximum amount of redirects is hit (default 5). + // Instead, we (arbitrarily) choose to allow for 3 redirects and then return a 200. + if (request.getRequestURI().endsWith("_moved_moved_moved")) { + response.setStatus(HttpServletResponse.SC_OK); + } else { + response.setStatus(HttpServletResponse.SC_FOUND); // 302 + response.setHeader("Location", request.getPathInfo() + "_moved"); + } + } else if ("GET".equalsIgnoreCase(request.getMethod())) { + response.setStatus(HttpServletResponse.SC_OK); + } else { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + } + + r.setHandled(true); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/HttpToHttpsRedirectTest.java b/client/src/test/java/org/asynchttpclient/HttpToHttpsRedirectTest.java new file mode 100644 index 0000000000..5795165343 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/HttpToHttpsRedirectTest.java @@ -0,0 +1,154 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +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; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; +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.assertNotNull; + +public class HttpToHttpsRedirectTest extends AbstractBasicTest { + + // FIXME super NOT threadsafe!!! + private static final AtomicBoolean redirectDone = new AtomicBoolean(false); + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector1 = addHttpConnector(server); + ServerConnector connector2 = addHttpsConnector(server); + server.setHandler(new Relative302Handler()); + server.start(); + port1 = connector1.getLocalPort(); + port2 = connector2.getLocalPort(); + logger.info("Local HTTP server started successfully"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + // FIXME find a way to make this threadsafe, other, set @RepeatedIfExceptionsTest(repeats = 5)(singleThreaded = true) + public void runAllSequentiallyBecauseNotThreadSafe() throws Exception { + httpToHttpsRedirect(); + httpToHttpsProperConfig(); + relativeLocationUrl(); + } + + // @Disabled + @RepeatedIfExceptionsTest(repeats = 5) + public void httpToHttpsRedirect() throws Exception { + redirectDone.getAndSet(false); + + AsyncHttpClientConfig cg = config() + .setMaxRedirects(5) + .setFollowRedirect(true) + .setUseInsecureTrustManager(true) + .build(); + try (AsyncHttpClient c = asyncHttpClient(cg)) { + Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", getTargetUrl2()).execute().get(); + assertNotNull(response); + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getHeader("X-httpToHttps"), "PASS"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void httpToHttpsProperConfig() throws Exception { + redirectDone.getAndSet(false); + + AsyncHttpClientConfig cg = config() + .setMaxRedirects(5) + .setFollowRedirect(true) + .setUseInsecureTrustManager(true) + .build(); + try (AsyncHttpClient c = asyncHttpClient(cg)) { + Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", getTargetUrl2() + "/test2").execute().get(); + assertNotNull(response); + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getHeader("X-httpToHttps"), "PASS"); + + // Test if the internal channel is downgraded to clean http. + response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", getTargetUrl2() + "/foo2").execute().get(); + assertNotNull(response); + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getHeader("X-httpToHttps"), "PASS"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void relativeLocationUrl() throws Exception { + redirectDone.getAndSet(false); + + AsyncHttpClientConfig cg = config() + .setMaxRedirects(5) + .setFollowRedirect(true) + .setUseInsecureTrustManager(true) + .build(); + try (AsyncHttpClient c = asyncHttpClient(cg)) { + Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", "/foo/test").execute().get(); + assertNotNull(response); + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getUri().toString(), getTargetUrl()); + } + } + + private static class Relative302Handler extends AbstractHandler { + + @Override + public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + + String param; + httpResponse.setContentType(TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + Enumeration e = httpRequest.getHeaderNames(); + while (e.hasMoreElements()) { + param = e.nextElement().toString(); + + if (param.startsWith("X-redirect") && !redirectDone.getAndSet(true)) { + httpResponse.addHeader("Location", httpRequest.getHeader(param)); + httpResponse.setStatus(302); + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + return; + } + } + + if ("https".equalsIgnoreCase(r.getScheme())) { + httpResponse.addHeader("X-httpToHttps", "PASS"); + redirectDone.getAndSet(false); + } + + httpResponse.setStatus(200); + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/IdleStateHandlerTest.java b/client/src/test/java/org/asynchttpclient/IdleStateHandlerTest.java new file mode 100644 index 0000000000..f229ca5abe --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/IdleStateHandlerTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +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; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.time.Duration; +import java.util.concurrent.ExecutionException; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.junit.jupiter.api.Assertions.fail; + +public class IdleStateHandlerTest extends AbstractBasicTest { + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector = addHttpConnector(server); + server.setHandler(new IdleStateHandler()); + server.start(); + port1 = connector.getLocalPort(); + logger.info("Local HTTP server started successfully"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void idleStateTest() throws Exception { + try (AsyncHttpClient c = asyncHttpClient(config().setPooledConnectionIdleTimeout(Duration.ofSeconds(10)))) { + c.prepareGet(getTargetUrl()).execute().get(); + } catch (ExecutionException e) { + fail("Should allow to finish processing request.", e); + } + } + + private static class IdleStateHandler extends AbstractHandler { + + @Override + public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + + try { + Thread.sleep(20 * 1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + httpResponse.setStatus(200); + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/ListenableFutureTest.java b/client/src/test/java/org/asynchttpclient/ListenableFutureTest.java new file mode 100644 index 0000000000..fb51bf551b --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ListenableFutureTest.java @@ -0,0 +1,76 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ListenableFutureTest extends AbstractBasicTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void testListenableFuture() throws Exception { + final AtomicInteger statusCode = new AtomicInteger(500); + try (AsyncHttpClient ahc = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(1); + final ListenableFuture future = ahc.prepareGet(getTargetUrl()).execute(); + future.addListener(() -> { + try { + statusCode.set(future.get().getStatusCode()); + latch.countDown(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + }, Executors.newFixedThreadPool(1)); + + latch.await(10, TimeUnit.SECONDS); + assertEquals(statusCode.get(), 200); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testListenableFutureAfterCompletion() throws Exception { + + final CountDownLatch latch = new CountDownLatch(1); + + try (AsyncHttpClient ahc = asyncHttpClient()) { + final ListenableFuture future = ahc.prepareGet(getTargetUrl()).execute(); + future.get(); + future.addListener(latch::countDown, Runnable::run); + } + + latch.await(10, TimeUnit.SECONDS); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testListenableFutureBeforeAndAfterCompletion() throws Exception { + + final CountDownLatch latch = new CountDownLatch(2); + + try (AsyncHttpClient ahc = asyncHttpClient()) { + final ListenableFuture future = ahc.prepareGet(getTargetUrl()).execute(); + future.addListener(latch::countDown, Runnable::run); + future.get(); + future.addListener(latch::countDown, Runnable::run); + } + + latch.await(10, TimeUnit.SECONDS); + } +} diff --git a/client/src/test/java/org/asynchttpclient/MultipleHeaderTest.java b/client/src/test/java/org/asynchttpclient/MultipleHeaderTest.java new file mode 100644 index 0000000000..cf6dbc3536 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/MultipleHeaderTest.java @@ -0,0 +1,201 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.HttpHeaders; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import javax.net.ServerSocketFactory; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.get; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Hubert Iwaniuk + */ +public class MultipleHeaderTest extends AbstractBasicTest { + private static ExecutorService executorService; + private static ServerSocket serverSocket; + private static Future voidFuture; + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + serverSocket = ServerSocketFactory.getDefault().createServerSocket(0); + port1 = serverSocket.getLocalPort(); + executorService = Executors.newFixedThreadPool(1); + voidFuture = executorService.submit(() -> { + Socket socket; + while ((socket = serverSocket.accept()) != null) { + InputStream inputStream = socket.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String req = reader.readLine().split(" ")[1]; + int i = inputStream.available(); + long l = inputStream.skip(i); + assertEquals(l, i); + socket.shutdownInput(); + if (req.endsWith("MultiEnt")) { + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(socket.getOutputStream()); + outputStreamWriter.append("HTTP/1.0 200 OK\n" + "Connection: close\n" + "Content-Type: text/plain; charset=iso-8859-1\n" + "X-Duplicated-Header: 2\n" + + "X-Duplicated-Header: 1\n" + "\n0\n"); + outputStreamWriter.flush(); + socket.shutdownOutput(); + } else if (req.endsWith("MultiOther")) { + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(socket.getOutputStream()); + outputStreamWriter.append("HTTP/1.0 200 OK\n" + "Connection: close\n" + "Content-Type: text/plain; charset=iso-8859-1\n" + "Content-Length: 1\n" + + "X-Forwarded-For: abc\n" + "X-Forwarded-For: def\n" + "\n0\n"); + outputStreamWriter.flush(); + socket.shutdownOutput(); + } + } + return null; + }); + } + + @Override + @AfterEach + public void tearDownGlobal() throws Exception { + voidFuture.cancel(true); + executorService.shutdownNow(); + serverSocket.close(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testMultipleOtherHeaders() throws Exception { + final String[] xffHeaders = {null, null}; + + try (AsyncHttpClient ahc = asyncHttpClient()) { + Request req = get("http://localhost/" + port1 + "/MultiOther").build(); + final CountDownLatch latch = new CountDownLatch(1); + ahc.executeRequest(req, new AsyncHandler() { + @Override + public void onThrowable(Throwable t) { + t.printStackTrace(System.out); + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart objectHttpResponseBodyPart) { + return State.CONTINUE; + } + + @Override + public State onStatusReceived(HttpResponseStatus objectHttpResponseStatus) { + return State.CONTINUE; + } + + @Override + public State onHeadersReceived(HttpHeaders response) { + int i = 0; + for (String header : response.getAll("X-Forwarded-For")) { + xffHeaders[i++] = header; + } + latch.countDown(); + return State.CONTINUE; + } + + @Override + public Void onCompleted() { + return null; + } + }).get(3, TimeUnit.SECONDS); + + if (!latch.await(2, TimeUnit.SECONDS)) { + fail("Time out"); + } + assertNotNull(xffHeaders[0]); + assertNotNull(xffHeaders[1]); + try { + assertEquals(xffHeaders[0], "abc"); + assertEquals(xffHeaders[1], "def"); + } catch (AssertionError ex) { + assertEquals(xffHeaders[1], "abc"); + assertEquals(xffHeaders[0], "def"); + } + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testMultipleEntityHeaders() throws Exception { + final String[] clHeaders = {null, null}; + + try (AsyncHttpClient ahc = asyncHttpClient()) { + Request req = get("http://localhost/" + port1 + "/MultiEnt").build(); + final CountDownLatch latch = new CountDownLatch(1); + ahc.executeRequest(req, new AsyncHandler() { + @Override + public void onThrowable(Throwable t) { + t.printStackTrace(System.out); + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart objectHttpResponseBodyPart) { + return State.CONTINUE; + } + + @Override + public State onStatusReceived(HttpResponseStatus objectHttpResponseStatus) { + return State.CONTINUE; + } + + @Override + public State onHeadersReceived(HttpHeaders response) { + try { + int i = 0; + for (String header : response.getAll("X-Duplicated-Header")) { + clHeaders[i++] = header; + } + } finally { + latch.countDown(); + } + return State.CONTINUE; + } + + @Override + public Void onCompleted() { + return null; + } + }).get(3, TimeUnit.SECONDS); + + if (!latch.await(2, TimeUnit.SECONDS)) { + fail("Time out"); + } + assertNotNull(clHeaders[0]); + assertNotNull(clHeaders[1]); + + // We can predict the order + try { + assertEquals(clHeaders[0], "2"); + assertEquals(clHeaders[1], "1"); + } catch (Throwable ex) { + assertEquals(clHeaders[0], "1"); + assertEquals(clHeaders[1], "2"); + } + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/NoNullResponseTest.java b/client/src/test/java/org/asynchttpclient/NoNullResponseTest.java new file mode 100644 index 0000000000..346f78d3ff --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/NoNullResponseTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2013 Ning, Inc. + * + * This program is licensed 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: + * + * 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 org.junit.jupiter.api.RepeatedTest; + +import java.time.Duration; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class NoNullResponseTest extends AbstractBasicTest { + private static final String GOOGLE_HTTPS_URL = "https://www.google.com/"; + + @RepeatedTest(4) + public void multipleSslRequestsWithDelayAndKeepAlive() throws Exception { + AsyncHttpClientConfig config = config() + .setFollowRedirect(true) + .setKeepAlive(true) + .setConnectTimeout(Duration.ofSeconds(10)) + .setPooledConnectionIdleTimeout(Duration.ofMinutes(1)) + .setRequestTimeout(Duration.ofSeconds(10)) + .setMaxConnectionsPerHost(-1) + .setMaxConnections(-1) + .build(); + + try (AsyncHttpClient client = asyncHttpClient(config)) { + final BoundRequestBuilder builder = client.prepareGet(GOOGLE_HTTPS_URL); + final Response response1 = builder.execute().get(); + Thread.sleep(4000); + final Response response2 = builder.execute().get(); + assertNotNull(response1); + assertNotNull(response2); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/NonAsciiContentLengthTest.java b/client/src/test/java/org/asynchttpclient/NonAsciiContentLengthTest.java new file mode 100644 index 0000000000..0d2aa562ce --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/NonAsciiContentLengthTest.java @@ -0,0 +1,83 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +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; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class NonAsciiContentLengthTest extends AbstractBasicTest { + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector = addHttpConnector(server); + server.setHandler(new AbstractHandler() { + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { + int MAX_BODY_SIZE = 1024; // Can only handle bodies of up to 1024 bytes. + byte[] b = new byte[MAX_BODY_SIZE]; + int offset = 0; + int numBytesRead; + try (ServletInputStream is = request.getInputStream()) { + while ((numBytesRead = is.read(b, offset, MAX_BODY_SIZE - offset)) != -1) { + offset += numBytesRead; + } + } + assertEquals(request.getContentLength(), offset); + response.setStatus(200); + response.setCharacterEncoding(request.getCharacterEncoding()); + response.setContentLength(request.getContentLength()); + try (ServletOutputStream os = response.getOutputStream()) { + os.write(b, 0, offset); + } + } + }); + server.start(); + port1 = connector.getLocalPort(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testNonAsciiContentLength() throws Exception { + execute("test"); + execute("\u4E00"); // Unicode CJK ideograph for one + } + + protected void execute(String body) throws IOException, InterruptedException, ExecutionException { + try (AsyncHttpClient client = asyncHttpClient()) { + BoundRequestBuilder r = client.preparePost(getTargetUrl()).setBody(body).setCharset(UTF_8); + Future f = r.execute(); + Response resp = f.get(); + assertEquals(resp.getStatusCode(), 200); + assertEquals(body, resp.getResponseBody(UTF_8)); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/ParamEncodingTest.java b/client/src/test/java/org/asynchttpclient/ParamEncodingTest.java new file mode 100644 index 0000000000..dcd27d46de --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ParamEncodingTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class ParamEncodingTest extends AbstractBasicTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void testParameters() throws Exception { + + String value = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKQLMNOPQRSTUVWXYZ1234567809`~!@#$%^&*()_+-=,.<>/?;:'\"[]{}\\| "; + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.preparePost("http://localhost/" + port1).addFormParam("test", value).execute(); + Response resp = f.get(10, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getHeader("X-Param"), value.trim()); + } + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new ParamEncoding(); + } + + private static class ParamEncoding extends AbstractHandler { + @Override + public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + if ("POST".equalsIgnoreCase(request.getMethod())) { + String p = request.getParameter("test"); + if (isNonEmpty(p)) { + response.setStatus(HttpServletResponse.SC_OK); + response.addHeader("X-Param", p); + } else { + response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE); + } + } else { // this handler is to handle POST request + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + response.getOutputStream().flush(); + response.getOutputStream().close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/PerRequestRelative302Test.java b/client/src/test/java/org/asynchttpclient/PerRequestRelative302Test.java new file mode 100644 index 0000000000..ae3eccf85d --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/PerRequestRelative302Test.java @@ -0,0 +1,169 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.uri.Uri; +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; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.net.ConnectException; +import java.util.Enumeration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; +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; + +public class PerRequestRelative302Test extends AbstractBasicTest { + + // FIXME super NOT threadsafe!!! + private static final AtomicBoolean isSet = new AtomicBoolean(false); + + private static int getPort(Uri uri) { + int port = uri.getPort(); + if (port == -1) { + port = "http".equals(uri.getScheme()) ? 80 : 443; + } + return port; + } + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector = addHttpConnector(server); + + server.setHandler(new Relative302Handler()); + server.start(); + port1 = connector.getLocalPort(); + logger.info("Local HTTP server started successfully"); + port2 = findFreePort(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + // FIXME threadsafe + public void runAllSequentiallyBecauseNotThreadSafe() throws Exception { + redirected302Test(); + notRedirected302Test(); + relativeLocationUrl(); + redirected302InvalidTest(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void redirected302Test() throws Exception { + isSet.getAndSet(false); + try (AsyncHttpClient c = asyncHttpClient()) { + Response response = c.prepareGet(getTargetUrl()).setFollowRedirect(true).setHeader("X-redirect", "https://www.google.com/").execute().get(); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + + String anyGooglePage = "https://www.google.com[^:]*:443"; + String baseUrl = getBaseUrl(response.getUri()); + + assertTrue(baseUrl.matches(anyGooglePage), "response does not show redirection to " + anyGooglePage); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void notRedirected302Test() throws Exception { + isSet.getAndSet(false); + try (AsyncHttpClient c = asyncHttpClient(config().setFollowRedirect(true))) { + Response response = c.prepareGet(getTargetUrl()).setFollowRedirect(false).setHeader("X-redirect", "http://www.microsoft.com/").execute().get(); + assertNotNull(response); + assertEquals(response.getStatusCode(), 302); + } + } + + private static String getBaseUrl(Uri uri) { + String url = uri.toString(); + int port = uri.getPort(); + if (port == -1) { + port = getPort(uri); + url = url.substring(0, url.length() - 1) + ':' + port; + } + return url.substring(0, url.lastIndexOf(':') + String.valueOf(port).length() + 1); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void redirected302InvalidTest() throws Exception { + isSet.getAndSet(false); + Exception e = null; + + try (AsyncHttpClient c = asyncHttpClient()) { + c.preparePost(getTargetUrl()).setFollowRedirect(true).setHeader("X-redirect", String.format("http://localhost:%d/", port2)).execute().get(); + } catch (ExecutionException ex) { + e = ex; + } + + assertNotNull(e); + Throwable cause = e.getCause(); + assertInstanceOf(ConnectException.class, cause); + assertTrue(cause.getMessage().contains(":" + port2)); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void relativeLocationUrl() throws Exception { + isSet.getAndSet(false); + + try (AsyncHttpClient c = asyncHttpClient()) { + Response response = c.preparePost(getTargetUrl()).setFollowRedirect(true).setHeader("X-redirect", "/foo/test").execute().get(); + assertNotNull(response); + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getUri().toString(), getTargetUrl()); + } + } + + private static class Relative302Handler extends AbstractHandler { + + @Override + public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + + String param; + httpResponse.setContentType(TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + Enumeration e = httpRequest.getHeaderNames(); + while (e.hasMoreElements()) { + param = e.nextElement().toString(); + + if (param.startsWith("X-redirect") && !isSet.getAndSet(true)) { + httpResponse.addHeader("Location", httpRequest.getHeader(param)); + httpResponse.setStatus(302); + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + return; + } + } + httpResponse.setStatus(200); + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java b/client/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java new file mode 100644 index 0000000000..bee7d0b676 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java @@ -0,0 +1,193 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.time.Duration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +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; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Per request timeout configuration test. + * + * @author Hubert Iwaniuk + */ +public class PerRequestTimeoutTest extends AbstractBasicTest { + private static final String MSG = "Enough is enough."; + + private static void checkTimeoutMessage(String message, boolean requestTimeout) { + if (requestTimeout) { + assertTrue(message.startsWith("Request timeout"), "error message indicates reason of error but got: " + message); + } else { + assertTrue(message.startsWith("Read timeout"), "error message indicates reason of error but got: " + message); + } + assertTrue(message.contains("localhost"), "error message contains remote host address but got: " + message); + assertTrue(message.contains("after 100 ms"), "error message contains timeout configuration value but got: " + message); + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new SlowHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRequestTimeout() throws IOException { + try (AsyncHttpClient client = asyncHttpClient()) { + Future responseFuture = client.prepareGet(getTargetUrl()) + .setRequestTimeout(Duration.ofMillis(100)) + .execute(); + Response response = responseFuture.get(2000, TimeUnit.MILLISECONDS); + assertNull(response); + } catch (InterruptedException e) { + fail("Interrupted.", e); + } catch (ExecutionException e) { + assertInstanceOf(TimeoutException.class, e.getCause()); + checkTimeoutMessage(e.getCause().getMessage(), true); + } catch (TimeoutException e) { + fail("Timeout.", e); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testReadTimeout() throws IOException { + try (AsyncHttpClient client = asyncHttpClient(config().setReadTimeout(Duration.ofMillis(100)))) { + Future responseFuture = client.prepareGet(getTargetUrl()).execute(); + Response response = responseFuture.get(2000, TimeUnit.MILLISECONDS); + assertNull(response); + } catch (InterruptedException e) { + fail("Interrupted.", e); + } catch (ExecutionException e) { + assertInstanceOf(TimeoutException.class, e.getCause()); + checkTimeoutMessage(e.getCause().getMessage(), false); + } catch (TimeoutException e) { + fail("Timeout.", e); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGlobalDefaultPerRequestInfiniteTimeout() throws IOException { + try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(Duration.ofMillis(100)))) { + Future responseFuture = client.prepareGet(getTargetUrl()) + .setRequestTimeout(Duration.ofMillis(-1)) + .execute(); + Response response = responseFuture.get(); + assertNotNull(response); + } catch (InterruptedException e) { + fail("Interrupted.", e); + } catch (ExecutionException e) { + assertInstanceOf(TimeoutException.class, e.getCause()); + checkTimeoutMessage(e.getCause().getMessage(), true); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGlobalRequestTimeout() throws IOException { + try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(Duration.ofMillis(100)))) { + Future responseFuture = client.prepareGet(getTargetUrl()).execute(); + Response response = responseFuture.get(2000, TimeUnit.MILLISECONDS); + assertNull(response); + } catch (InterruptedException e) { + fail("Interrupted.", e); + } catch (ExecutionException e) { + assertInstanceOf(TimeoutException.class, e.getCause()); + checkTimeoutMessage(e.getCause().getMessage(), true); + } catch (TimeoutException e) { + fail("Timeout.", e); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGlobalIdleTimeout() throws IOException { + final long[] times = {-1, -1}; + + try (AsyncHttpClient client = asyncHttpClient(config().setPooledConnectionIdleTimeout(Duration.ofSeconds(2)))) { + Future responseFuture = client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandler() { + @Override + public Response onCompleted(Response response) { + return response; + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart content) throws Exception { + times[0] = unpreciseMillisTime(); + return super.onBodyPartReceived(content); + } + + @Override + public void onThrowable(Throwable t) { + times[1] = unpreciseMillisTime(); + super.onThrowable(t); + } + }); + Response response = responseFuture.get(); + assertNotNull(response); + assertEquals(response.getResponseBody(), MSG + MSG); + } catch (InterruptedException e) { + fail("Interrupted.", e); + } catch (ExecutionException e) { + logger.info(String.format("\n@%dms Last body part received\n@%dms Connection killed\n %dms difference.", times[0], times[1], times[1] - times[0])); + fail("Timeouted on idle.", e); + } + } + + private static class SlowHandler extends AbstractHandler { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException { + response.setStatus(HttpServletResponse.SC_OK); + final AsyncContext asyncContext = request.startAsync(); + new Thread(() -> { + try { + Thread.sleep(1500); + response.getOutputStream().print(MSG); + response.getOutputStream().flush(); + } catch (InterruptedException | IOException e) { + logger.error(e.getMessage(), e); + } + }).start(); + new Thread(() -> { + try { + Thread.sleep(3000); + response.getOutputStream().print(MSG); + response.getOutputStream().flush(); + asyncContext.complete(); + } catch (InterruptedException | IOException e) { + logger.error(e.getMessage(), e); + } + }).start(); + baseRequest.setHandled(true); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/PostRedirectGetTest.java b/client/src/test/java/org/asynchttpclient/PostRedirectGetTest.java new file mode 100644 index 0000000000..ae752760b8 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/PostRedirectGetTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2012-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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.filter.FilterContext; +import org.asynchttpclient.filter.ResponseFilter; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.post; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class PostRedirectGetTest extends AbstractBasicTest { + + @Override + public AbstractHandler configureHandler() throws Exception { + return new PostRedirectGetHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postRedirectGet302Test() throws Exception { + doTestPositive(302); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postRedirectGet302StrictTest() throws Exception { + doTestNegative(302, true); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postRedirectGet303Test() throws Exception { + doTestPositive(303); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postRedirectGet301Test() throws Exception { + doTestPositive(301); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postRedirectGet307Test() throws Exception { + doTestNegative(307, false); + } + + // --------------------------------------------------------- Private Methods + + private void doTestNegative(final int status, boolean strict) throws Exception { + + ResponseFilter responseFilter = new ResponseFilter() { + @Override + public FilterContext filter(FilterContext ctx) { + // pass on the x-expect-get and remove the x-redirect + // headers if found in the response + ctx.getResponseHeaders().get("x-expect-post"); + ctx.getRequest().getHeaders().add("x-expect-post", "true"); + ctx.getRequest().getHeaders().remove("x-redirect"); + return ctx; + } + }; + + try (AsyncHttpClient p = asyncHttpClient(config().setFollowRedirect(true).setStrict302Handling(strict).addResponseFilter(responseFilter))) { + Request request = post(getTargetUrl()).addFormParam("q", "a b").addHeader("x-redirect", +status + "@" + "http://localhost/" + port1 + "/foo/bar/baz").addHeader("x-negative", "true").build(); + Future responseFuture = p.executeRequest(request, new AsyncCompletionHandler() { + + @Override + public Integer onCompleted(Response response) { + return response.getStatusCode(); + } + + @Override + public void onThrowable(Throwable t) { + t.printStackTrace(); + fail("Unexpected exception: " + t.getMessage(), t); + } + + }); + int statusCode = responseFuture.get(); + assertEquals(statusCode, 200); + } + } + + private void doTestPositive(final int status) throws Exception { + + ResponseFilter responseFilter = new ResponseFilter() { + @Override + public FilterContext filter(FilterContext ctx) { + // pass on the x-expect-get and remove the x-redirect + // headers if found in the response + ctx.getResponseHeaders().get("x-expect-get"); + ctx.getRequest().getHeaders().add("x-expect-get", "true"); + ctx.getRequest().getHeaders().remove("x-redirect"); + return ctx; + } + }; + + try (AsyncHttpClient p = asyncHttpClient(config().setFollowRedirect(true).addResponseFilter(responseFilter))) { + Request request = post(getTargetUrl()).addFormParam("q", "a b").addHeader("x-redirect", +status + "@" + "http://localhost/" + port1 + "/foo/bar/baz").build(); + Future responseFuture = p.executeRequest(request, new AsyncCompletionHandler() { + + @Override + public Integer onCompleted(Response response) { + return response.getStatusCode(); + } + + @Override + public void onThrowable(Throwable t) { + t.printStackTrace(); + fail("Unexpected exception: " + t.getMessage(), t); + } + + }); + int statusCode = responseFuture.get(); + assertEquals(statusCode, 200); + } + } + + // ---------------------------------------------------------- Nested Classes + + public static class PostRedirectGetHandler extends AbstractHandler { + + final AtomicInteger counter = new AtomicInteger(); + + @Override + public void handle(String pathInContext, org.eclipse.jetty.server.Request request, HttpServletRequest httpRequest, + HttpServletResponse httpResponse) throws IOException, ServletException { + + final boolean expectGet = httpRequest.getHeader("x-expect-get") != null; + final boolean expectPost = httpRequest.getHeader("x-expect-post") != null; + if (expectGet) { + final String method = request.getMethod(); + if (!"GET".equals(method)) { + httpResponse.sendError(500, "Incorrect method. Expected GET, received " + method); + return; + } + httpResponse.setStatus(200); + httpResponse.getOutputStream().write("OK".getBytes()); + httpResponse.getOutputStream().flush(); + return; + } + if (expectPost) { + final String method = request.getMethod(); + if (!"POST".equals(method)) { + httpResponse.sendError(500, "Incorrect method. Expected POST, received " + method); + return; + } + httpResponse.setStatus(200); + httpResponse.getOutputStream().write("OK".getBytes()); + httpResponse.getOutputStream().flush(); + return; + } + + String header = httpRequest.getHeader("x-redirect"); + if (header != null) { + // format for header is | + String[] parts = header.split("@"); + int redirectCode; + try { + redirectCode = Integer.parseInt(parts[0]); + } catch (Exception ex) { + ex.printStackTrace(); + httpResponse.sendError(500, "Unable to parse redirect code"); + return; + } + httpResponse.setStatus(redirectCode); + if (httpRequest.getHeader("x-negative") == null) { + httpResponse.addHeader("x-expect-get", "true"); + } else { + httpResponse.addHeader("x-expect-post", "true"); + } + httpResponse.setContentLength(0); + httpResponse.addHeader("Location", parts[1] + counter.getAndIncrement()); + httpResponse.getOutputStream().flush(); + return; + } + + httpResponse.sendError(500); + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/PostWithQueryStringTest.java b/client/src/test/java/org/asynchttpclient/PostWithQueryStringTest.java new file mode 100644 index 0000000000..a78ef4a848 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/PostWithQueryStringTest.java @@ -0,0 +1,123 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Tests POST request with Query String. + * + * @author Hubert Iwaniuk + */ +public class PostWithQueryStringTest extends AbstractBasicTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void postWithQueryString() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.preparePost("http://localhost/" + port1 + "/?a=b").setBody("abc".getBytes()).execute(); + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postWithNullQueryParam() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.preparePost("http://localhost/" + port1 + "/?a=b&c&d=e").setBody("abc".getBytes()).execute(new AsyncCompletionHandlerBase() { + + @Override + public State onStatusReceived(final HttpResponseStatus status) throws Exception { + if (!status.getUri().toUrl().equals("http://localhost/" + port1 + "/?a=b&c&d=e")) { + throw new IOException("failed to parse the query properly"); + } + return super.onStatusReceived(status); + } + + }); + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void postWithEmptyParamsQueryString() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.preparePost("http://localhost/" + port1 + "/?a=b&c=&d=e").setBody("abc".getBytes()).execute(new AsyncCompletionHandlerBase() { + + @Override + public State onStatusReceived(final HttpResponseStatus status) throws Exception { + if (!status.getUri().toUrl().equals("http://localhost/" + port1 + "/?a=b&c=&d=e")) { + throw new IOException("failed to parse the query properly"); + } + return super.onStatusReceived(status); + } + + }); + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + } + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new PostWithQueryStringHandler(); + } + + /** + * POST with QueryString server part. + */ + private static class PostWithQueryStringHandler extends AbstractHandler { + @Override + public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + if ("POST".equalsIgnoreCase(request.getMethod())) { + String qs = request.getQueryString(); + if (isNonEmpty(qs) && request.getContentLength() == 3) { + ServletInputStream is = request.getInputStream(); + response.setStatus(HttpServletResponse.SC_OK); + byte[] buf = new byte[is.available()]; + is.readLine(buf, 0, is.available()); + ServletOutputStream os = response.getOutputStream(); + os.println(new String(buf)); + os.flush(); + os.close(); + } else { + response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE); + } + } else { // this handler is to handle POST request + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/QueryParametersTest.java b/client/src/test/java/org/asynchttpclient/QueryParametersTest.java new file mode 100644 index 0000000000..fd71cc1b95 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/QueryParametersTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Testing query parameters support. + * + * @author Hubert Iwaniuk + */ +public class QueryParametersTest extends AbstractBasicTest { + + @Override + public AbstractHandler configureHandler() throws Exception { + return new QueryStringHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testQueryParameters() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.prepareGet("http://localhost/" + port1).addQueryParam("a", "1").addQueryParam("b", "2").execute(); + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getHeader("a"), "1"); + assertEquals(resp.getHeader("b"), "2"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testUrlRequestParametersEncoding() throws Exception { + String URL = getTargetUrl() + "?q="; + String REQUEST_PARAM = "github github \ngithub"; + + try (AsyncHttpClient client = asyncHttpClient()) { + String requestUrl2 = URL + URLEncoder.encode(REQUEST_PARAM, UTF_8); + logger.info("Executing request [{}] ...", requestUrl2); + Response response = client.prepareGet(requestUrl2).execute().get(); + String s = URLDecoder.decode(response.getHeader("q"), UTF_8); + assertEquals(s, REQUEST_PARAM); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void urlWithColonTest() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + String query = "test:colon:"; + Response response = c.prepareGet(String.format("http://localhost:%d/foo/test/colon?q=%s", port1, query)).setHeader("Content-Type", "text/html").execute().get(TIMEOUT, TimeUnit.SECONDS); + + assertEquals(response.getHeader("q"), query); + } + } + + private static class QueryStringHandler extends AbstractHandler { + @Override + public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + if ("GET".equalsIgnoreCase(request.getMethod())) { + String qs = request.getQueryString(); + if (isNonEmpty(qs)) { + for (String qnv : qs.split("&")) { + String[] nv = qnv.split("="); + response.addHeader(nv[0], nv[1]); + } + response.setStatus(HttpServletResponse.SC_OK); + } else { + response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE); + } + } else { // this handler is to handle POST request + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + r.setHandled(true); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/RC1KTest.java b/client/src/test/java/org/asynchttpclient/RC1KTest.java new file mode 100644 index 0000000000..36f9bf1b91 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/RC1KTest.java @@ -0,0 +1,149 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.HttpHeaders; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +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; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Reverse C1K Problem test. + * + * @author Hubert Iwaniuk + */ +public class RC1KTest extends AbstractBasicTest { + private static final int C1K = 1000; + private static final String ARG_HEADER = "Arg"; + private static final int SRV_COUNT = 10; + private static final Server[] servers = new Server[SRV_COUNT]; + private static int[] ports = new int[SRV_COUNT]; + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + ports = new int[SRV_COUNT]; + for (int i = 0; i < SRV_COUNT; i++) { + Server server = new Server(); + ServerConnector connector = addHttpConnector(server); + server.setHandler(configureHandler()); + server.start(); + servers[i] = server; + ports[i] = connector.getLocalPort(); + } + logger.info("Local HTTP servers started successfully"); + } + + @Override + @AfterEach + public void tearDownGlobal() throws Exception { + for (Server srv : servers) { + srv.stop(); + } + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new AbstractHandler() { + @Override + public void handle(String s, Request r, HttpServletRequest req, HttpServletResponse resp) throws IOException { + resp.setContentType("text/pain"); + String arg = s.substring(1); + resp.setHeader(ARG_HEADER, arg); + resp.setStatus(200); + resp.getOutputStream().print(arg); + resp.getOutputStream().flush(); + resp.getOutputStream().close(); + } + }; + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 10 * 60 * 1000) + public void rc10kProblem() throws Exception { + try (AsyncHttpClient ahc = asyncHttpClient(config().setMaxConnectionsPerHost(C1K).setKeepAlive(true))) { + List> resps = new ArrayList<>(C1K); + int i = 0; + while (i < C1K) { + resps.add(ahc.prepareGet(String.format("http://localhost:%d/%d", ports[i % SRV_COUNT], i)).execute(new MyAsyncHandler(i++))); + } + i = 0; + for (Future fResp : resps) { + Integer resp = fResp.get(); + assertNotNull(resp); + assertEquals(resp.intValue(), i++); + } + } + } + + private static class MyAsyncHandler implements AsyncHandler { + private final String arg; + private final AtomicInteger result = new AtomicInteger(-1); + + MyAsyncHandler(int i) { + arg = String.format("%d", i); + } + + @Override + public void onThrowable(Throwable t) { + logger.warn("onThrowable called.", t); + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart event) { + String s = new String(event.getBodyPartBytes()); + result.compareAndSet(-1, Integer.valueOf(s.trim().isEmpty() ? "-1" : s)); + return State.CONTINUE; + } + + @Override + public State onStatusReceived(HttpResponseStatus event) { + assertEquals(event.getStatusCode(), 200); + return State.CONTINUE; + } + + @Override + public State onHeadersReceived(HttpHeaders event) { + assertEquals(event.get(ARG_HEADER), arg); + return State.CONTINUE; + } + + @Override + public Integer onCompleted() { + return result.get(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/RealmTest.java b/client/src/test/java/org/asynchttpclient/RealmTest.java new file mode 100644 index 0000000000..6c85ca8af5 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/RealmTest.java @@ -0,0 +1,111 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.uri.Uri; +import org.asynchttpclient.util.StringUtils; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +import static java.nio.charset.StandardCharsets.UTF_16; +import static org.asynchttpclient.Dsl.basicAuthRealm; +import static org.asynchttpclient.Dsl.digestAuthRealm; +import static org.asynchttpclient.Dsl.realm; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class RealmTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void testClone() { + Realm orig = basicAuthRealm("user", "pass").setCharset(UTF_16) + .setUsePreemptiveAuth(true) + .setRealmName("realm") + .setAlgorithm("algo").build(); + + Realm clone = realm(orig).build(); + assertEquals(clone.getPrincipal(), orig.getPrincipal()); + assertEquals(clone.getPassword(), orig.getPassword()); + assertEquals(clone.getCharset(), orig.getCharset()); + assertEquals(clone.isUsePreemptiveAuth(), orig.isUsePreemptiveAuth()); + assertEquals(clone.getRealmName(), orig.getRealmName()); + assertEquals(clone.getAlgorithm(), orig.getAlgorithm()); + assertEquals(clone.getScheme(), orig.getScheme()); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOldDigestEmptyString() throws Exception { + testOldDigest(""); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOldDigestNull() throws Exception { + testOldDigest(null); + } + + private void testOldDigest(String qop) throws Exception { + String user = "user"; + String pass = "pass"; + String realm = "realm"; + String nonce = "nonce"; + String method = "GET"; + Uri uri = Uri.create("http://ahc.io/foo"); + Realm orig = digestAuthRealm(user, pass) + .setNonce(nonce) + .setUri(uri) + .setMethodName(method) + .setRealmName(realm) + .setQop(qop) + .build(); + + String ha1 = getMd5(user + ':' + realm + ':' + pass); + String ha2 = getMd5(method + ':' + uri.getPath()); + String expectedResponse = getMd5(ha1 + ':' + nonce + ':' + ha2); + + assertEquals(orig.getResponse(), expectedResponse); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testStrongDigest() throws Exception { + String user = "user"; + String pass = "pass"; + String realm = "realm"; + String nonce = "nonce"; + String method = "GET"; + Uri uri = Uri.create("http://ahc.io/foo"); + String qop = "auth"; + Realm orig = digestAuthRealm(user, pass) + .setNonce(nonce) + .setUri(uri) + .setMethodName(method) + .setRealmName(realm) + .setQop(qop) + .build(); + + String nc = orig.getNc(); + String cnonce = orig.getCnonce(); + String ha1 = getMd5(user + ':' + realm + ':' + pass); + String ha2 = getMd5(method + ':' + uri.getPath()); + String expectedResponse = getMd5(ha1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2); + + assertEquals(orig.getResponse(), expectedResponse); + } + + private String getMd5(String what) throws Exception { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(what.getBytes(StandardCharsets.ISO_8859_1)); + byte[] hash = md.digest(); + return StringUtils.toHexString(hash); + } +} diff --git a/client/src/test/java/org/asynchttpclient/RedirectBodyTest.java b/client/src/test/java/org/asynchttpclient/RedirectBodyTest.java new file mode 100644 index 0000000000..461c7a06a0 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/RedirectBodyTest.java @@ -0,0 +1,124 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.netty.handler.codec.http.HttpHeaderNames.LOCATION; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class RedirectBodyTest extends AbstractBasicTest { + + private static volatile boolean redirectAlreadyPerformed; + private static volatile String receivedContentType; + + @BeforeEach + public void setUp() { + redirectAlreadyPerformed = false; + receivedContentType = null; + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new AbstractHandler() { + @Override + public void handle(String pathInContext, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException { + + String redirectHeader = httpRequest.getHeader("X-REDIRECT"); + if (redirectHeader != null && !redirectAlreadyPerformed) { + redirectAlreadyPerformed = true; + httpResponse.setStatus(Integer.valueOf(redirectHeader)); + httpResponse.setContentLength(0); + httpResponse.setHeader(LOCATION.toString(), getTargetUrl()); + + } else { + receivedContentType = request.getContentType(); + httpResponse.setStatus(200); + int len = request.getContentLength(); + httpResponse.setContentLength(len); + if (len > 0) { + byte[] buffer = new byte[len]; + IOUtils.read(request.getInputStream(), buffer); + httpResponse.getOutputStream().write(buffer); + } + } + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + } + }; + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void regular301LosesBody() throws Exception { + try (AsyncHttpClient c = asyncHttpClient(config().setFollowRedirect(true))) { + String body = "hello there"; + String contentType = "text/plain; charset=UTF-8"; + + Response response = c.preparePost(getTargetUrl()).setHeader(CONTENT_TYPE, contentType).setBody(body).setHeader("X-REDIRECT", "301").execute().get(TIMEOUT, TimeUnit.SECONDS); + assertEquals(response.getResponseBody(), ""); + assertNull(receivedContentType); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void regular302LosesBody() throws Exception { + try (AsyncHttpClient c = asyncHttpClient(config().setFollowRedirect(true))) { + String body = "hello there"; + String contentType = "text/plain; charset=UTF-8"; + + Response response = c.preparePost(getTargetUrl()).setHeader(CONTENT_TYPE, contentType).setBody(body).setHeader("X-REDIRECT", "302").execute().get(TIMEOUT, TimeUnit.SECONDS); + assertEquals(response.getResponseBody(), ""); + assertNull(receivedContentType); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void regular302StrictKeepsBody() throws Exception { + try (AsyncHttpClient c = asyncHttpClient(config().setFollowRedirect(true).setStrict302Handling(true))) { + String body = "hello there"; + String contentType = "text/plain; charset=UTF-8"; + + Response response = c.preparePost(getTargetUrl()).setHeader(CONTENT_TYPE, contentType).setBody(body).setHeader("X-REDIRECT", "302").execute().get(TIMEOUT, TimeUnit.SECONDS); + assertEquals(response.getResponseBody(), body); + assertEquals(receivedContentType, contentType); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void regular307KeepsBody() throws Exception { + try (AsyncHttpClient c = asyncHttpClient(config().setFollowRedirect(true))) { + String body = "hello there"; + String contentType = "text/plain; charset=UTF-8"; + + Response response = c.preparePost(getTargetUrl()).setHeader(CONTENT_TYPE, contentType).setBody(body).setHeader("X-REDIRECT", "307").execute().get(TIMEOUT, TimeUnit.SECONDS); + assertEquals(response.getResponseBody(), body); + assertEquals(receivedContentType, contentType); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/RedirectConnectionUsageTest.java b/client/src/test/java/org/asynchttpclient/RedirectConnectionUsageTest.java new file mode 100644 index 0000000000..01b37b86cd --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/RedirectConnectionUsageTest.java @@ -0,0 +1,126 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.io.OutputStream; +import java.time.Duration; +import java.util.Date; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.get; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Test for multithreaded url fetcher calls that use two separate sets of ssl certificates. This then tests that the certificate settings do not clash (override each other), + * resulting in the peer not authenticated exception + * + * @author dominict + */ +public class RedirectConnectionUsageTest extends AbstractBasicTest { + + private String baseUrl; + private String servletEndpointRedirectUrl; + + @BeforeEach + public void setUp() throws Exception { + server = new Server(); + ServerConnector connector = addHttpConnector(server); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.addServlet(new ServletHolder(new MockRedirectHttpServlet()), "/redirect/*"); + context.addServlet(new ServletHolder(new MockFullResponseHttpServlet()), "/*"); + server.setHandler(context); + + server.start(); + port1 = connector.getLocalPort(); + + baseUrl = "http://localhost/" + ':' + port1; + servletEndpointRedirectUrl = baseUrl + "/redirect"; + } + + /** + * Tests that after a redirect the final url in the response reflect the redirect + */ + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetRedirectFinalUrl() throws Exception { + + AsyncHttpClientConfig config = config() + .setKeepAlive(true) + .setMaxConnectionsPerHost(1) + .setMaxConnections(1) + .setConnectTimeout(Duration.ofSeconds(1)) + .setRequestTimeout(Duration.ofSeconds(1)) + .setFollowRedirect(true) + .build(); + + try (AsyncHttpClient c = asyncHttpClient(config)) { + ListenableFuture response = c.executeRequest(get(servletEndpointRedirectUrl)); + Response res = response.get(); + assertNotNull(res.getResponseBody()); + assertEquals(res.getUri().toString(), baseUrl + "/overthere"); + } + } + + @SuppressWarnings("serial") + static + class MockRedirectHttpServlet extends HttpServlet { + @Override + public void service(HttpServletRequest req, HttpServletResponse res) throws IOException { + res.sendRedirect("/overthere"); + } + } + + @SuppressWarnings("serial") + static + class MockFullResponseHttpServlet extends HttpServlet { + + private static final String contentType = "text/xml"; + private static final String xml = ""; + + @Override + public void service(HttpServletRequest req, HttpServletResponse res) throws IOException { + String xmlToReturn = String.format(xml, new Date()); + + res.setStatus(200); + res.addHeader("Content-Type", contentType); + res.addHeader("X-Method", req.getMethod()); + res.addHeader("MultiValue", "1"); + res.addHeader("MultiValue", "2"); + res.addHeader("MultiValue", "3"); + + OutputStream os = res.getOutputStream(); + + byte[] retVal = xmlToReturn.getBytes(); + res.setContentLength(retVal.length); + os.write(retVal); + os.close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/Relative302Test.java b/client/src/test/java/org/asynchttpclient/Relative302Test.java new file mode 100644 index 0000000000..074930791f --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/Relative302Test.java @@ -0,0 +1,176 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.uri.Uri; +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; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.URI; +import java.util.Enumeration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; +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; + +public class Relative302Test extends AbstractBasicTest { + private static final AtomicBoolean isSet = new AtomicBoolean(false); + + private static int getPort(Uri uri) { + int port = uri.getPort(); + if (port == -1) { + port = "http".equals(uri.getScheme()) ? 80 : 443; + } + return port; + } + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector = addHttpConnector(server); + server.setHandler(new Relative302Handler()); + server.start(); + port1 = connector.getLocalPort(); + logger.info("Local HTTP server started successfully"); + port2 = findFreePort(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testAllSequentiallyBecauseNotThreadSafe() throws Exception { + redirected302Test(); + redirected302InvalidTest(); + absolutePathRedirectTest(); + relativePathRedirectTest(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void redirected302Test() throws Exception { + isSet.getAndSet(false); + + try (AsyncHttpClient c = asyncHttpClient(config().setFollowRedirect(true))) { + Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", "http://www.google.com/").execute().get(); + assertNotNull(response); + assertEquals(response.getStatusCode(), 200); + + String baseUrl = getBaseUrl(response.getUri()); + assertTrue(baseUrl.startsWith("http://www.google./"), "response does not show redirection to a google subdomain, got " + baseUrl); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void redirected302InvalidTest() throws Exception { + isSet.getAndSet(false); + + Exception e = null; + + try (AsyncHttpClient c = asyncHttpClient(config().setFollowRedirect(true))) { + c.prepareGet(getTargetUrl()).setHeader("X-redirect", String.format("http://localhost:%d/", port2)).execute().get(); + } catch (ExecutionException ex) { + e = ex; + } + + assertNotNull(e); + Throwable cause = e.getCause(); + assertInstanceOf(ConnectException.class, cause); + assertTrue(cause.getMessage().contains(":" + port2)); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void absolutePathRedirectTest() throws Exception { + isSet.getAndSet(false); + + try (AsyncHttpClient c = asyncHttpClient(config().setFollowRedirect(true))) { + String redirectTarget = "/bar/test"; + String destinationUrl = new URI(getTargetUrl()).resolve(redirectTarget).toString(); + + Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", redirectTarget).execute().get(); + assertNotNull(response); + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getUri().toString(), destinationUrl); + + logger.debug("{} was redirected to {}", redirectTarget, destinationUrl); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void relativePathRedirectTest() throws Exception { + isSet.getAndSet(false); + + try (AsyncHttpClient c = asyncHttpClient(config().setFollowRedirect(true))) { + String redirectTarget = "bar/test1"; + String destinationUrl = new URI(getTargetUrl()).resolve(redirectTarget).toString(); + + Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", redirectTarget).execute().get(); + assertNotNull(response); + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getUri().toString(), destinationUrl); + + logger.debug("{} was redirected to {}", redirectTarget, destinationUrl); + } + } + + private static String getBaseUrl(Uri uri) { + String url = uri.toString(); + int port = uri.getPort(); + if (port == -1) { + port = getPort(uri); + url = url.substring(0, url.length() - 1) + ':' + port; + } + return url.substring(0, url.lastIndexOf(':') + String.valueOf(port).length() + 1); + } + + private static class Relative302Handler extends AbstractHandler { + + @Override + public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + + String param; + httpResponse.setStatus(200); + httpResponse.setContentType(TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + Enumeration e = httpRequest.getHeaderNames(); + while (e.hasMoreElements()) { + param = e.nextElement().toString(); + + if (param.startsWith("X-redirect") && !isSet.getAndSet(true)) { + httpResponse.addHeader("Location", httpRequest.getHeader(param)); + httpResponse.setStatus(302); + break; + } + } + httpResponse.setContentLength(0); + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java b/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java new file mode 100644 index 0000000000..34e79121d3 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/RequestBuilderTest.java @@ -0,0 +1,223 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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 io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.cookie.Cookie; +import io.netty.handler.codec.http.cookie.DefaultCookie; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.singletonList; +import static org.asynchttpclient.Dsl.get; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RequestBuilderTest { + + private static final String SAFE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-_*."; + private static final String HEX_CHARS = "0123456789ABCDEF"; + + @RepeatedIfExceptionsTest(repeats = 5) + public void testEncodesQueryParameters() { + String[] values = {"abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKQLMNOPQRSTUVWXYZ", "1234567890", "1234567890", "`~!@#$%^&*()", "`~!@#$%^&*()", "_+-=,.<>/?", + "_+-=,.<>/?", ";:'\"[]{}\\| ", ";:'\"[]{}\\| "}; + + /* + * as per RFC-5849 (Oauth), and RFC-3986 (percent encoding) we MUST + * encode everything except for "safe" characters; and nothing but them. + * Safe includes ascii letters (upper and lower case), digits (0 - 9) + * and FOUR special characters: hyphen ('-'), underscore ('_'), tilde + * ('~') and period ('.')). Everything else must be percent-encoded, + * byte-by-byte, using UTF-8 encoding (meaning three-byte Unicode/UTF-8 + * code points are encoded as three three-letter percent-encode + * entities). + */ + for (String value : values) { + RequestBuilder builder = get("http://example.com/").addQueryParam("name", value); + + StringBuilder sb = new StringBuilder(); + for (int i = 0, len = value.length(); i < len; ++i) { + char c = value.charAt(i); + if (SAFE_CHARS.indexOf(c) >= 0) { + sb.append(c); + } else { + int hi = c >> 4; + int lo = c & 0xF; + sb.append('%').append(HEX_CHARS.charAt(hi)).append(HEX_CHARS.charAt(lo)); + } + } + String expValue = sb.toString(); + Request request = builder.build(); + assertEquals(request.getUrl(), "http://example.com/?name=" + expValue); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testChaining() { + Request request = get("http://foo.com/").addQueryParam("x", "value").build(); + Request request2 = request.toBuilder().build(); + + assertEquals(request2.getUri(), request.getUri()); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testParsesQueryParams() { + Request request = get("http://foo.com/?param1=value1").addQueryParam("param2", "value2").build(); + + assertEquals(request.getUrl(), "http://foo.com/?param1=value1¶m2=value2"); + List params = request.getQueryParams(); + assertEquals(params.size(), 2); + assertEquals(params.get(0), new Param("param1", "value1")); + assertEquals(params.get(1), new Param("param2", "value2")); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testUserProvidedRequestMethod() { + Request req = new RequestBuilder("ABC").setUrl("http://foo.com/").build(); + assertEquals(req.getMethod(), "ABC"); + assertEquals(req.getUrl(), "http://foo.com/"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPercentageEncodedUserInfo() { + final Request req = get("http://hello:wor%20ld@foo.com/").build(); + assertEquals(req.getMethod(), "GET"); + assertEquals(req.getUrl(), "http://hello:wor%20ld@foo.com/"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testContentTypeCharsetToBodyEncoding() { + final Request req = get("http://localhost/").setHeader("Content-Type", "application/json; charset=utf-8").build(); + assertEquals(req.getCharset(), UTF_8); + final Request req2 = get("http://localhost/").setHeader("Content-Type", "application/json; charset=\"utf-8\"").build(); + assertEquals(req2.getCharset(), UTF_8); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultMethod() { + RequestBuilder requestBuilder = new RequestBuilder(); + String defaultMethodName = HttpMethod.GET.name(); + assertEquals(requestBuilder.method, defaultMethodName, "Default HTTP method should be " + defaultMethodName); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSetHeaders() { + RequestBuilder requestBuilder = new RequestBuilder(); + assertTrue(requestBuilder.headers.isEmpty(), "Headers should be empty by default."); + + Map> headers = new HashMap<>(); + headers.put("Content-Type", Collections.singleton("application/json")); + requestBuilder.setHeaders(headers); + assertTrue(requestBuilder.headers.contains("Content-Type"), "headers set by setHeaders have not been set"); + assertEquals(requestBuilder.headers.get("Content-Type"), "application/json", "header value incorrect"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testAddOrReplaceCookies() { + 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.addOrReplaceCookie(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.addOrReplaceCookie(cookie2); + assertEquals(requestBuilder.cookies.size(), 1, "cookies size should remain 1 as we just replaced a cookie with same name"); + assertEquals(requestBuilder.cookies.get(0), cookie2, "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.addOrReplaceCookie(cookie3); + 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(); + requestBuilder.setQueryParams(singletonList(new Param("key", "value"))); + requestBuilder.setUrl("http://localhost/"); + Request request = requestBuilder.build(); + assertEquals(request.getUrl(), "http://localhost/?key=value"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSettingHeadersUsingMapWithStringKeys() { + Map> headers = new HashMap<>(); + headers.put("X-Forwarded-For", singletonList("10.0.0.1")); + + RequestBuilder requestBuilder = new RequestBuilder(); + requestBuilder.setHeaders(headers); + requestBuilder.setUrl("http://localhost/"); + Request request = requestBuilder.build(); + assertEquals(request.getHeaders().get("X-Forwarded-For"), "10.0.0.1"); + } +} diff --git a/client/src/test/java/org/asynchttpclient/RetryRequestTest.java b/client/src/test/java/org/asynchttpclient/RetryRequestTest.java new file mode 100644 index 0000000000..07e9f2ca86 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/RetryRequestTest.java @@ -0,0 +1,84 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.exception.RemotelyClosedException; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.io.OutputStream; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class RetryRequestTest extends AbstractBasicTest { + + @Override + protected String getTargetUrl() { + return String.format("http://localhost:%d/", port1); + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new SlowAndBigHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testMaxRetry() { + try (AsyncHttpClient ahc = asyncHttpClient(config().setMaxRequestRetry(0))) { + ahc.executeRequest(ahc.prepareGet(getTargetUrl()).build()).get(); + fail(); + } catch (Exception t) { + assertEquals(t.getCause(), RemotelyClosedException.INSTANCE); + } + } + + public static class SlowAndBigHandler extends AbstractHandler { + + @Override + public void handle(String pathInContext, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + + int load = 100; + httpResponse.setStatus(200); + httpResponse.setContentLength(load); + httpResponse.setContentType("application/octet-stream"); + + httpResponse.flushBuffer(); + + OutputStream os = httpResponse.getOutputStream(); + for (int i = 0; i < load; i++) { + os.write(i % 255); + + try { + Thread.sleep(300); + } catch (InterruptedException ex) { + // nuku + } + + if (i > load / 10) { + httpResponse.sendError(500); + } + } + + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + } + } +} 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 0000000000..08c150c08a --- /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/ThreadNameTest.java b/client/src/test/java/org/asynchttpclient/ThreadNameTest.java new file mode 100644 index 0000000000..a6a151aa99 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ThreadNameTest.java @@ -0,0 +1,69 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; + +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests configured client name is used for thread names. + * + * @author Stepan Koltsov + */ +public class ThreadNameTest extends AbstractBasicTest { + + private static Thread[] getThreads() { + int count = Thread.activeCount() + 1; + for (; ; ) { + Thread[] threads = new Thread[count]; + int filled = Thread.enumerate(threads); + if (filled < threads.length) { + return Arrays.copyOf(threads, filled); + } + + count *= 2; + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testThreadName() throws Exception { + String threadPoolName = "ahc-" + (new Random().nextLong() & 0x7fffffffffffffffL); + try (AsyncHttpClient client = asyncHttpClient(config().setThreadPoolName(threadPoolName))) { + Future f = client.prepareGet("http://localhost/" + port1 + '/').execute(); + f.get(3, TimeUnit.SECONDS); + + // We cannot assert that all threads are created with specified name, + // so we checking that at least one thread is. + boolean found = false; + for (Thread thread : getThreads()) { + if (thread.getName().startsWith(threadPoolName)) { + found = true; + break; + } + } + + assertTrue(found, "must found threads starting with random string " + threadPoolName); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/channel/ConnectionPoolTest.java b/client/src/test/java/org/asynchttpclient/channel/ConnectionPoolTest.java new file mode 100644 index 0000000000..878a047d14 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/channel/ConnectionPoolTest.java @@ -0,0 +1,267 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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.channel; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncCompletionHandler; +import org.asynchttpclient.AsyncCompletionHandlerBase; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.ListenableFuture; +import org.asynchttpclient.RequestBuilder; +import org.asynchttpclient.Response; +import org.asynchttpclient.test.EventCollectingHandler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.junit.jupiter.api.function.ThrowingSupplier; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.get; +import static org.asynchttpclient.test.EventCollectingHandler.COMPLETED_EVENT; +import static org.asynchttpclient.test.EventCollectingHandler.CONNECTION_OFFER_EVENT; +import static org.asynchttpclient.test.EventCollectingHandler.CONNECTION_POOLED_EVENT; +import static org.asynchttpclient.test.EventCollectingHandler.CONNECTION_POOL_EVENT; +import static org.asynchttpclient.test.EventCollectingHandler.HEADERS_RECEIVED_EVENT; +import static org.asynchttpclient.test.EventCollectingHandler.HEADERS_WRITTEN_EVENT; +import static org.asynchttpclient.test.EventCollectingHandler.REQUEST_SEND_EVENT; +import static org.asynchttpclient.test.EventCollectingHandler.STATUS_RECEIVED_EVENT; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +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.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +public class ConnectionPoolTest extends AbstractBasicTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void testMaxTotalConnections() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().setKeepAlive(true).setMaxConnections(1))) { + String url = getTargetUrl(); + + for (int i = 0; i < 3; i++) { + logger.info("{} requesting url [{}]...", i, url); + + Response response = assertDoesNotThrow(new ThrowingSupplier() { + @Override + public Response get() throws Throwable { + return client.prepareGet(url).execute().get(); + } + }); + + assertNotNull(response); + logger.info("{} response [{}].", i, response); + } + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testMaxTotalConnectionsException() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().setKeepAlive(true).setMaxConnections(1))) { + String url = getTargetUrl(); + + List> futures = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + logger.info("{} requesting url [{}]...", i, url); + futures.add(client.prepareGet(url).execute()); + } + + Exception exception = null; + for (ListenableFuture future : futures) { + try { + future.get(); + } catch (Exception ex) { + exception = ex; + break; + } + } + + assertNotNull(exception); + assertInstanceOf(ExecutionException.class, exception); + } + } + + @RepeatedIfExceptionsTest(repeats = 3) + public void asyncDoGetKeepAliveHandlerTest_channelClosedDoesNotFail() throws Exception { + for (int i = 0; i < 10; i++) { + try (AsyncHttpClient client = asyncHttpClient()) { + final CountDownLatch l = new CountDownLatch(2); + final Map remoteAddresses = new ConcurrentHashMap<>(); + + AsyncCompletionHandler handler = new AsyncCompletionHandlerAdapter() { + + @Override + public Response onCompleted(Response response) { + logger.debug("ON COMPLETED INVOKED " + response.getHeader("X-KEEP-ALIVE")); + try { + assertEquals(200, response.getStatusCode()); + remoteAddresses.put(response.getHeader("X-KEEP-ALIVE"), true); + } finally { + l.countDown(); + } + return response; + } + + @Override + public void onThrowable(Throwable t) { + try { + super.onThrowable(t); + } finally { + l.countDown(); + } + } + }; + + client.prepareGet(getTargetUrl()).execute(handler).get(); + server.stop(); + + // Jetty 9.4.8 doesn't properly stop and restart (recreates ReservedThreadExecutors on start but still point to old offers threads to old ones) + // instead of restarting, we create a fresh new one and have it bind on the same port + server = new Server(); + ServerConnector newConnector = addHttpConnector(server); + // make sure connector will restart with the port as it's originally dynamically allocated + newConnector.setPort(port1); + server.setHandler(configureHandler()); + server.start(); + + client.prepareGet(getTargetUrl()).execute(handler); + + if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { + fail("Timed out"); + } + + assertEquals(remoteAddresses.size(), 2); + } + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void multipleMaxConnectionOpenTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().setKeepAlive(true).setConnectTimeout(Duration.ofSeconds(5)).setMaxConnections(1))) { + String body = "hello there"; + + // once + Response response = client.preparePost(getTargetUrl()).setBody(body).execute().get(TIMEOUT, TimeUnit.SECONDS); + assertEquals(response.getResponseBody(), body); + + // twice + assertThrows(ExecutionException.class, () -> client.preparePost(String.format("http://localhost:%d/foo/test", port2)) + .setBody(body) + .execute() + .get(TIMEOUT, TimeUnit.SECONDS)); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void multipleMaxConnectionOpenTestWithQuery() throws Exception { + try (AsyncHttpClient c = asyncHttpClient(config().setKeepAlive(true).setConnectTimeout(Duration.ofSeconds(5)).setMaxConnections(1))) { + String body = "hello there"; + + // once + Response response = c.preparePost(getTargetUrl() + "?foo=bar").setBody(body).execute().get(TIMEOUT, TimeUnit.SECONDS); + assertEquals(response.getResponseBody(), "foo_" + body); + + // twice + response = c.preparePost(getTargetUrl()).setBody(body).execute().get(TIMEOUT, TimeUnit.SECONDS); + assertNotNull(response); + assertEquals(response.getStatusCode(), 200); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void asyncHandlerOnThrowableTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + final AtomicInteger count = new AtomicInteger(); + final String THIS_IS_NOT_FOR_YOU = "This is not for you"; + final CountDownLatch latch = new CountDownLatch(16); + for (int i = 0; i < 16; i++) { + client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerBase() { + @Override + public Response onCompleted(Response response) throws Exception { + throw new Exception(THIS_IS_NOT_FOR_YOU); + } + }); + + client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerBase() { + @Override + public void onThrowable(Throwable t) { + if (t.getMessage() != null && t.getMessage().equalsIgnoreCase(THIS_IS_NOT_FOR_YOU)) { + count.incrementAndGet(); + } + } + + @Override + public Response onCompleted(Response response) { + latch.countDown(); + return response; + } + }); + } + latch.await(TIMEOUT, TimeUnit.SECONDS); + assertEquals(count.get(), 0); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void nonPoolableConnectionReleaseSemaphoresTest() throws Throwable { + + RequestBuilder request = get(getTargetUrl()).setHeader("Connection", "close"); + + try (AsyncHttpClient client = asyncHttpClient(config().setMaxConnections(6).setMaxConnectionsPerHost(3))) { + client.executeRequest(request).get(); + Thread.sleep(1000); + client.executeRequest(request).get(); + Thread.sleep(1000); + client.executeRequest(request).get(); + Thread.sleep(1000); + client.executeRequest(request).get(); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPooledEventsFired() throws Exception { + RequestBuilder request = get("http://localhost/" + port1 + "/Test"); + + try (AsyncHttpClient client = asyncHttpClient()) { + EventCollectingHandler firstHandler = new EventCollectingHandler(); + client.executeRequest(request, firstHandler).get(3, TimeUnit.SECONDS); + firstHandler.waitForCompletion(3, TimeUnit.SECONDS); + + EventCollectingHandler secondHandler = new EventCollectingHandler(); + client.executeRequest(request, secondHandler).get(3, TimeUnit.SECONDS); + secondHandler.waitForCompletion(3, TimeUnit.SECONDS); + + Object[] expectedEvents = {CONNECTION_POOL_EVENT, CONNECTION_POOLED_EVENT, REQUEST_SEND_EVENT, HEADERS_WRITTEN_EVENT, STATUS_RECEIVED_EVENT, + HEADERS_RECEIVED_EVENT, CONNECTION_OFFER_EVENT, COMPLETED_EVENT}; + + assertArrayEquals(secondHandler.firedEvents.toArray(), expectedEvents, "Got " + Arrays.toString(secondHandler.firedEvents.toArray())); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/channel/MaxConnectionsInThreadsTest.java b/client/src/test/java/org/asynchttpclient/channel/MaxConnectionsInThreadsTest.java new file mode 100644 index 0000000000..d82aa08c41 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/channel/MaxConnectionsInThreadsTest.java @@ -0,0 +1,181 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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.channel; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncCompletionHandlerBase; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.OutputStream; +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class MaxConnectionsInThreadsTest extends AbstractBasicTest { + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector = addHttpConnector(server); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + server.setHandler(context); + context.addServlet(new ServletHolder(new MockTimeoutHttpServlet()), "/timeout/*"); + + server.start(); + port1 = connector.getLocalPort(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testMaxConnectionsWithinThreads() throws Exception { + + String[] urls = {getTargetUrl(), getTargetUrl()}; + + AsyncHttpClientConfig config = config() + .setConnectTimeout(Duration.ofSeconds(1)) + .setRequestTimeout(Duration.ofSeconds(5)) + .setKeepAlive(true) + .setMaxConnections(1) + .setMaxConnectionsPerHost(1) + .build(); + + final CountDownLatch inThreadsLatch = new CountDownLatch(2); + final AtomicInteger failedCount = new AtomicInteger(); + + try (AsyncHttpClient client = asyncHttpClient(config)) { + for (final String url : urls) { + Thread t = new Thread() { + + @Override + public void run() { + client.prepareGet(url).execute(new AsyncCompletionHandlerBase() { + @Override + public Response onCompleted(Response response) throws Exception { + Response r = super.onCompleted(response); + inThreadsLatch.countDown(); + return r; + } + + @Override + public void onThrowable(Throwable t) { + super.onThrowable(t); + failedCount.incrementAndGet(); + inThreadsLatch.countDown(); + } + }); + } + }; + t.start(); + } + + inThreadsLatch.await(); + assertEquals(failedCount.get(), 1, "Max Connections should have been reached when launching from concurrent threads"); + + final CountDownLatch notInThreadsLatch = new CountDownLatch(2); + failedCount.set(0); + + for (final String url : urls) { + client.prepareGet(url).execute(new AsyncCompletionHandlerBase() { + @Override + public Response onCompleted(Response response) throws Exception { + Response r = super.onCompleted(response); + notInThreadsLatch.countDown(); + return r; + } + + @Override + public void onThrowable(Throwable t) { + super.onThrowable(t); + failedCount.incrementAndGet(); + notInThreadsLatch.countDown(); + } + }); + } + + notInThreadsLatch.await(); + assertEquals(failedCount.get(), 1, "Max Connections should have been reached when launching from main thread"); + } + } + + @Override + public String getTargetUrl() { + return "http://localhost/" + port1 + "/timeout/"; + } + + @SuppressWarnings("serial") + public static class MockTimeoutHttpServlet extends HttpServlet { + private static final Logger LOGGER = LoggerFactory.getLogger(MockTimeoutHttpServlet.class); + private static final String contentType = "text/plain"; + private static final long DEFAULT_TIMEOUT = 2000; + + @Override + public void service(HttpServletRequest req, HttpServletResponse res) throws IOException { + res.setStatus(200); + res.addHeader("Content-Type", contentType); + + long sleepTime; + try { + sleepTime = Integer.parseInt(req.getParameter("timeout")); + } catch (NumberFormatException e) { + sleepTime = DEFAULT_TIMEOUT; + } + + try { + LOGGER.debug("======================================="); + LOGGER.debug("Servlet is sleeping for: " + sleepTime); + LOGGER.debug("======================================="); + Thread.sleep(sleepTime); + LOGGER.debug("======================================="); + LOGGER.debug("Servlet is awake for"); + LOGGER.debug("======================================="); + } catch (Exception e) { + // + } + + res.setHeader("XXX", "TripleX"); + + byte[] retVal = "1".getBytes(); + OutputStream os = res.getOutputStream(); + + res.setContentLength(retVal.length); + os.write(retVal); + os.close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/channel/MaxTotalConnectionTest.java b/client/src/test/java/org/asynchttpclient/channel/MaxTotalConnectionTest.java new file mode 100644 index 0000000000..6094f5bdb9 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/channel/MaxTotalConnectionTest.java @@ -0,0 +1,119 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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.channel; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncCompletionHandlerBase; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.ListenableFuture; +import org.asynchttpclient.Response; + +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class MaxTotalConnectionTest extends AbstractBasicTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void testMaxTotalConnectionsExceedingException() throws IOException { + String[] urls = {"https://google.com/", "https://github.com/"}; + + AsyncHttpClientConfig config = config() + .setConnectTimeout(Duration.ofSeconds(1)) + .setRequestTimeout(Duration.ofSeconds(5)) + .setKeepAlive(false) + .setMaxConnections(1) + .setMaxConnectionsPerHost(1) + .build(); + + try (AsyncHttpClient client = asyncHttpClient(config)) { + List> futures = new ArrayList<>(); + for (String url : urls) { + futures.add(client.prepareGet(url).execute()); + } + + boolean caughtError = false; + int i; + for (i = 0; i < urls.length; i++) { + try { + futures.get(i).get(); + } catch (Exception e) { + // assert that 2nd request fails, because + // maxTotalConnections=1 + caughtError = true; + break; + } + } + + assertEquals(1, i); + assertTrue(caughtError); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testMaxTotalConnections() throws Exception { + String[] urls = {"https://www.google.com/", "https://www.youtube.com/"}; + + final CountDownLatch latch = new CountDownLatch(2); + final AtomicReference ex = new AtomicReference<>(); + final AtomicReference failedUrl = new AtomicReference<>(); + + AsyncHttpClientConfig config = config() + .setConnectTimeout(Duration.ofSeconds(1)) + .setRequestTimeout(Duration.ofSeconds(5)) + .setKeepAlive(false) + .setMaxConnections(2) + .setMaxConnectionsPerHost(1) + .build(); + + try (AsyncHttpClient client = asyncHttpClient(config)) { + for (String url : urls) { + final String thisUrl = url; + client.prepareGet(url).execute(new AsyncCompletionHandlerBase() { + @Override + public Response onCompleted(Response response) throws Exception { + Response r = super.onCompleted(response); + latch.countDown(); + return r; + } + + @Override + public void onThrowable(Throwable t) { + super.onThrowable(t); + ex.set(t); + failedUrl.set(thisUrl); + latch.countDown(); + } + }); + } + + latch.await(); + assertNull(ex.get()); + assertNull(failedUrl.get()); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/filter/FilterTest.java b/client/src/test/java/org/asynchttpclient/filter/FilterTest.java new file mode 100644 index 0000000000..fba6c4ec01 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/filter/FilterTest.java @@ -0,0 +1,183 @@ +/* + * 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 io.github.artsok.RepeatedIfExceptionsTest; +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.Response; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class FilterTest extends AbstractBasicTest { + + @Override + public AbstractHandler configureHandler() throws Exception { + return new BasicHandler(); + } + + @Override + public String getTargetUrl() { + return String.format("http://localhost:%d/foo/test", port1); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicTest() throws Exception { + try (AsyncHttpClient c = asyncHttpClient(config().addRequestFilter(new ThrottleRequestFilter(100)))) { + Response response = c.preparePost(getTargetUrl()).execute().get(); + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void loadThrottleTest() throws Exception { + try (AsyncHttpClient c = asyncHttpClient(config().addRequestFilter(new ThrottleRequestFilter(10)))) { + List> futures = new ArrayList<>(); + for (int i = 0; i < 200; i++) { + futures.add(c.preparePost(getTargetUrl()).execute()); + } + + for (Future future : futures) { + Response r = future.get(); + assertNotNull(r); + assertEquals(200, r.getStatusCode()); + } + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void maxConnectionsText() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().addRequestFilter(new ThrottleRequestFilter(0, 1000)))) { + assertThrows(Exception.class, () -> client.preparePost(getTargetUrl()).execute().get()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicResponseFilterTest() throws Exception { + + ResponseFilter responseFilter = new ResponseFilter() { + @Override + public FilterContext filter(FilterContext ctx) { + return ctx; + } + }; + + try (AsyncHttpClient client = asyncHttpClient(config().addResponseFilter(responseFilter))) { + Response response = client.preparePost(getTargetUrl()).execute().get(); + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void replayResponseFilterTest() throws Exception { + final AtomicBoolean replay = new AtomicBoolean(true); + ResponseFilter responseFilter = new ResponseFilter() { + + @Override + public FilterContext filter(FilterContext ctx) { + if (replay.getAndSet(false)) { + org.asynchttpclient.Request request = ctx.getRequest().toBuilder().addHeader("X-Replay", "true").build(); + return new FilterContext.FilterContextBuilder(ctx.getAsyncHandler(), request).replayRequest(true).build(); + } + return ctx; + } + }; + + try (AsyncHttpClient client = asyncHttpClient(config().addResponseFilter(responseFilter))) { + Response response = client.preparePost(getTargetUrl()).execute().get(); + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertEquals("true", response.getHeader("X-Replay")); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void replayStatusCodeResponseFilterTest() throws Exception { + final AtomicBoolean replay = new AtomicBoolean(true); + ResponseFilter responseFilter = new ResponseFilter() { + + @Override + public FilterContext filter(FilterContext ctx) { + if (ctx.getResponseStatus() != null && ctx.getResponseStatus().getStatusCode() == 200 && replay.getAndSet(false)) { + org.asynchttpclient.Request request = ctx.getRequest().toBuilder().addHeader("X-Replay", "true").build(); + return new FilterContext.FilterContextBuilder(ctx.getAsyncHandler(), request).replayRequest(true).build(); + } + return ctx; + } + }; + + try (AsyncHttpClient client = asyncHttpClient(config().addResponseFilter(responseFilter))) { + Response response = client.preparePost(getTargetUrl()).execute().get(); + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertEquals("true", response.getHeader("X-Replay")); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void replayHeaderResponseFilterTest() throws Exception { + final AtomicBoolean replay = new AtomicBoolean(true); + ResponseFilter responseFilter = new ResponseFilter() { + @Override + public FilterContext filter(FilterContext ctx) { + if (ctx.getResponseHeaders() != null && "Pong".equals(ctx.getResponseHeaders().get("Ping")) && replay.getAndSet(false)) { + org.asynchttpclient.Request request = ctx.getRequest().toBuilder().addHeader("Ping", "Pong").build(); + return new FilterContext.FilterContextBuilder(ctx.getAsyncHandler(), request).replayRequest(true).build(); + } + return ctx; + } + }; + + try (AsyncHttpClient client = asyncHttpClient(config().addResponseFilter(responseFilter))) { + Response response = client.preparePost(getTargetUrl()).addHeader("Ping", "Pong").execute().get(); + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertEquals("Pong", response.getHeader("Ping")); + } + } + + private static class BasicHandler extends AbstractHandler { + + @Override + public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + Enumeration e = httpRequest.getHeaderNames(); + String param; + while (e.hasMoreElements()) { + param = e.nextElement().toString(); + httpResponse.addHeader(param, httpRequest.getHeader(param)); + } + + httpResponse.setStatus(200); + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/handler/BodyDeferringAsyncHandlerTest.java b/client/src/test/java/org/asynchttpclient/handler/BodyDeferringAsyncHandlerTest.java new file mode 100644 index 0000000000..1705dcc636 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/handler/BodyDeferringAsyncHandlerTest.java @@ -0,0 +1,300 @@ +/* + * 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.handler; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.BoundRequestBuilder; +import org.asynchttpclient.ListenableFuture; +import org.asynchttpclient.Response; +import org.asynchttpclient.exception.RemotelyClosedException; +import org.asynchttpclient.handler.BodyDeferringAsyncHandler.BodyDeferringInputStream; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_OCTET_STREAM; +import static org.apache.commons.io.IOUtils.copy; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.findFreePort; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class BodyDeferringAsyncHandlerTest extends AbstractBasicTest { + + static final int CONTENT_LENGTH_VALUE = 100000; + + @Override + public AbstractHandler configureHandler() throws Exception { + return new SlowAndBigHandler(); + } + + private static AsyncHttpClientConfig getAsyncHttpClientConfig() { + // for this test brevity's sake, we are limiting to 1 retries + return config().setMaxRequestRetry(0).setRequestTimeout(Duration.ofSeconds(10)).build(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void deferredSimple() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(getAsyncHttpClientConfig())) { + BoundRequestBuilder r = client.prepareGet(getTargetUrl()); + + CountingOutputStream cos = new CountingOutputStream(); + BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(cos); + Future f = r.execute(bdah); + Response resp = bdah.getResponse(); + assertNotNull(resp); + assertEquals(HttpServletResponse.SC_OK, resp.getStatusCode()); + assertEquals(String.valueOf(CONTENT_LENGTH_VALUE), resp.getHeader(CONTENT_LENGTH)); + + // we got headers only, it's probably not all yet here (we have BIG file + // downloading) + assertTrue(cos.getByteCount() <= CONTENT_LENGTH_VALUE); + + // now be polite and wait for body arrival too (otherwise we would be + // dropping the "line" on server) + assertDoesNotThrow(() -> f.get()); + + // it all should be here now + assertEquals(cos.getByteCount(), CONTENT_LENGTH_VALUE); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void deferredSimpleWithFailure() throws Throwable { + try (AsyncHttpClient client = asyncHttpClient(getAsyncHttpClientConfig())) { + BoundRequestBuilder requestBuilder = client.prepareGet(getTargetUrl()).addHeader("X-FAIL-TRANSFER", Boolean.TRUE.toString()); + + CountingOutputStream cos = new CountingOutputStream(); + BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(cos); + Future f = requestBuilder.execute(bdah); + Response resp = bdah.getResponse(); + assertNotNull(resp); + assertEquals(HttpServletResponse.SC_OK, resp.getStatusCode()); + assertEquals(String.valueOf(CONTENT_LENGTH_VALUE), resp.getHeader(CONTENT_LENGTH)); + // we got headers only, it's probably not all yet here (we have BIG file + // downloading) + assertTrue(cos.getByteCount() <= CONTENT_LENGTH_VALUE); + + // now be polite and wait for body arrival too (otherwise we would be + // dropping the "line" on server) + try { + assertThrows(ExecutionException.class, () -> f.get()); + } catch (Exception ex) { + assertInstanceOf(RemotelyClosedException.class, ex.getCause()); + } + assertNotEquals(CONTENT_LENGTH_VALUE, cos.getByteCount()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void deferredInputStreamTrick() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(getAsyncHttpClientConfig())) { + BoundRequestBuilder r = client.prepareGet(getTargetUrl()); + + PipedOutputStream pos = new PipedOutputStream(); + PipedInputStream pis = new PipedInputStream(pos); + BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(pos); + + Future f = r.execute(bdah); + + BodyDeferringInputStream is = new BodyDeferringInputStream(f, bdah, pis); + + Response resp = is.getAsapResponse(); + assertNotNull(resp); + assertEquals(HttpServletResponse.SC_OK, resp.getStatusCode()); + assertEquals(String.valueOf(CONTENT_LENGTH_VALUE), resp.getHeader(CONTENT_LENGTH)); + // "consume" the body, but our code needs input stream + CountingOutputStream cos = new CountingOutputStream(); + try { + copy(is, cos); + } finally { + is.close(); + cos.close(); + } + + // now we don't need to be polite, since consuming and closing + // BodyDeferringInputStream does all. + // it all should be here now + assertEquals(CONTENT_LENGTH_VALUE, cos.getByteCount()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void deferredInputStreamTrickWithFailure() throws Throwable { + try (AsyncHttpClient client = asyncHttpClient(getAsyncHttpClientConfig())) { + BoundRequestBuilder r = client.prepareGet(getTargetUrl()).addHeader("X-FAIL-TRANSFER", Boolean.TRUE.toString()); + PipedOutputStream pos = new PipedOutputStream(); + PipedInputStream pis = new PipedInputStream(pos); + BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(pos); + + Future f = r.execute(bdah); + + BodyDeferringInputStream is = new BodyDeferringInputStream(f, bdah, pis); + + Response resp = is.getAsapResponse(); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getHeader(CONTENT_LENGTH), String.valueOf(CONTENT_LENGTH_VALUE)); + // "consume" the body, but our code needs input stream + CountingOutputStream cos = new CountingOutputStream(); + + try (is; cos) { + copy(is, cos); + } catch (Exception ex) { + assertInstanceOf(RemotelyClosedException.class, ex.getCause()); + } + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void deferredInputStreamTrickWithCloseConnectionAndRetry() throws Throwable { + try (AsyncHttpClient client = asyncHttpClient(config().setMaxRequestRetry(1).setRequestTimeout(Duration.ofSeconds(10)).build())) { + BoundRequestBuilder r = client.prepareGet(getTargetUrl()).addHeader("X-CLOSE-CONNECTION", Boolean.TRUE.toString()); + PipedOutputStream pos = new PipedOutputStream(); + PipedInputStream pis = new PipedInputStream(pos); + BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(pos); + + Future f = r.execute(bdah); + + BodyDeferringInputStream is = new BodyDeferringInputStream(f, bdah, pis); + + Response resp = is.getAsapResponse(); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getHeader(CONTENT_LENGTH), String.valueOf(CONTENT_LENGTH_VALUE)); + // "consume" the body, but our code needs input stream + CountingOutputStream cos = new CountingOutputStream(); + + try (is; cos) { + copy(is, cos); + } catch (Exception ex) { + assertInstanceOf(UnsupportedOperationException.class, ex.getCause()); + } + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testConnectionRefused() throws Exception { + int newPortWithoutAnyoneListening = findFreePort(); + try (AsyncHttpClient client = asyncHttpClient(getAsyncHttpClientConfig())) { + BoundRequestBuilder r = client.prepareGet("http://localhost/" + newPortWithoutAnyoneListening + "/testConnectionRefused"); + + CountingOutputStream cos = new CountingOutputStream(); + BodyDeferringAsyncHandler bdah = new BodyDeferringAsyncHandler(cos); + r.execute(bdah); + assertThrows(IOException.class, () -> bdah.getResponse()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPipedStreams() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(getAsyncHttpClientConfig())) { + PipedOutputStream pout = new PipedOutputStream(); + try (PipedInputStream pin = new PipedInputStream(pout)) { + BodyDeferringAsyncHandler handler = new BodyDeferringAsyncHandler(pout); + ListenableFuture respFut = client.prepareGet(getTargetUrl()).execute(handler); + + Response resp = handler.getResponse(); + assertEquals(200, resp.getStatusCode()); + + try (BodyDeferringInputStream is = new BodyDeferringInputStream(respFut, handler, pin)) { + String body = IOUtils.toString(is, StandardCharsets.UTF_8); + System.out.println("Body: " + body); + assertTrue(body.contains("ABCDEF")); + } + } + } + } + + public static class SlowAndBigHandler extends AbstractHandler { + + @Override + public void handle(String pathInContext, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + httpResponse.setStatus(200); + httpResponse.setContentLength(CONTENT_LENGTH_VALUE); + httpResponse.setContentType(APPLICATION_OCTET_STREAM.toString()); + + httpResponse.flushBuffer(); + + final boolean wantConnectionClose = httpRequest.getHeader("X-CLOSE-CONNECTION") != null; + final boolean wantFailure = httpRequest.getHeader("X-FAIL-TRANSFER") != null; + final boolean wantSlow = httpRequest.getHeader("X-SLOW") != null; + + OutputStream os = httpResponse.getOutputStream(); + for (int i = 0; i < CONTENT_LENGTH_VALUE; i++) { + os.write(i % 255); + + if (wantSlow) { + try { + Thread.sleep(300); + } catch (InterruptedException ex) { + // nuku + } + } + + if (i > CONTENT_LENGTH_VALUE / 2) { + if (wantFailure) { + // kaboom + // yes, response is committed, but Jetty does aborts and + // drops connection + httpResponse.sendError(500); + break; + } else if (wantConnectionClose) { + // kaboom^2 + httpResponse.getOutputStream().close(); + } + } + } + + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + } + } + + // a /dev/null but counting how many bytes it ditched + public static class CountingOutputStream extends OutputStream { + private int byteCount; + + @Override + public void write(int b) { + // /dev/null + byteCount++; + } + + int getByteCount() { + return byteCount; + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/handler/resumable/MapResumableProcessor.java b/client/src/test/java/org/asynchttpclient/handler/resumable/MapResumableProcessor.java new file mode 100644 index 0000000000..e82fa1ef87 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/handler/resumable/MapResumableProcessor.java @@ -0,0 +1,56 @@ +/* + * 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.handler.resumable; + +import org.asynchttpclient.handler.resumable.ResumableAsyncHandler.ResumableProcessor; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Benjamin Hanzelmann + */ +public class MapResumableProcessor implements ResumableProcessor { + + private final Map map = new HashMap<>(); + + @Override + public void put(String key, long transferredBytes) { + map.put(key, transferredBytes); + } + + @Override + public void remove(String key) { + map.remove(key); + } + + /** + * NOOP + */ + @Override + public void save(Map map) { + + } + + /** + * NOOP + */ + @Override + public Map load() { + return Collections.unmodifiableMap(map); + } +} diff --git a/client/src/test/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcessorTest.java b/client/src/test/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcessorTest.java new file mode 100644 index 0000000000..d8c1bd4f29 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcessorTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010 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.handler.resumable; + +import io.github.artsok.RepeatedIfExceptionsTest; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Benjamin Hanzelmann + */ +public class PropertiesBasedResumableProcessorTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSaveLoad() { + PropertiesBasedResumableProcessor processor = new PropertiesBasedResumableProcessor(); + processor.put("http://localhost/test.url", 15L); + processor.put("http://localhost/test2.url", 50L); + processor.save(null); + processor = new PropertiesBasedResumableProcessor(); + + Map map = processor.load(); + assertEquals(2, map.size()); + assertEquals(Long.valueOf(15L), map.get("http://localhost/test.url")); + assertEquals(Long.valueOf(50L), map.get("http://localhost/test2.url")); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRemove() { + PropertiesBasedResumableProcessor processor = new PropertiesBasedResumableProcessor(); + processor.put("http://localhost/test.url", 15L); + processor.put("http://localhost/test2.url", 50L); + processor.remove("http://localhost/test.url"); + processor.save(null); + processor = new PropertiesBasedResumableProcessor(); + + Map propertiesMap = processor.load(); + assertEquals(1, propertiesMap.size()); + assertEquals(Long.valueOf(50L), propertiesMap.get("http://localhost/test2.url")); + } +} diff --git a/client/src/test/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandlerTest.java b/client/src/test/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandlerTest.java new file mode 100644 index 0000000000..e142587576 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandlerTest.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2010 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.handler.resumable; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaders; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHandler.State; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.Request; +import org.asynchttpclient.Response; +import org.asynchttpclient.uri.Uri; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static io.netty.handler.codec.http.HttpHeaderNames.RANGE; +import static org.asynchttpclient.Dsl.get; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author Benjamin Hanzelmann + */ +public class ResumableAsyncHandlerTest { + + public static final byte[] T = new byte[0]; + + @RepeatedIfExceptionsTest(repeats = 5) + public void testAdjustRange() { + MapResumableProcessor processor = new MapResumableProcessor(); + + ResumableAsyncHandler handler = new ResumableAsyncHandler(processor); + Request request = get("http://test/url").build(); + Request newRequest = handler.adjustRequestRange(request); + assertEquals(request.getUri(), newRequest.getUri()); + String rangeHeader = newRequest.getHeaders().get(RANGE); + assertNull(rangeHeader); + + processor.put("http://test/url", 5000); + newRequest = handler.adjustRequestRange(request); + assertEquals(request.getUri(), newRequest.getUri()); + rangeHeader = newRequest.getHeaders().get(RANGE); + assertEquals("bytes=5000-", rangeHeader); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOnStatusReceivedOkStatus() throws Exception { + MapResumableProcessor processor = new MapResumableProcessor(); + ResumableAsyncHandler handler = new ResumableAsyncHandler(processor); + HttpResponseStatus responseStatus200 = mock(HttpResponseStatus.class); + when(responseStatus200.getStatusCode()).thenReturn(200); + when(responseStatus200.getUri()).thenReturn(mock(Uri.class)); + State state = handler.onStatusReceived(responseStatus200); + assertEquals(AsyncHandler.State.CONTINUE, state, "Status should be CONTINUE for a OK response"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOnStatusReceived206Status() throws Exception { + MapResumableProcessor processor = new MapResumableProcessor(); + ResumableAsyncHandler handler = new ResumableAsyncHandler(processor); + HttpResponseStatus responseStatus206 = mock(HttpResponseStatus.class); + when(responseStatus206.getStatusCode()).thenReturn(206); + when(responseStatus206.getUri()).thenReturn(mock(Uri.class)); + State state = handler.onStatusReceived(responseStatus206); + assertEquals(AsyncHandler.State.CONTINUE, state, "Status should be CONTINUE for a 'Partial Content' response"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOnStatusReceivedOkStatusWithDecoratedAsyncHandler() throws Exception { + HttpResponseStatus mockResponseStatus = mock(HttpResponseStatus.class); + when(mockResponseStatus.getStatusCode()).thenReturn(200); + when(mockResponseStatus.getUri()).thenReturn(mock(Uri.class)); + + @SuppressWarnings("unchecked") + AsyncHandler decoratedAsyncHandler = mock(AsyncHandler.class); + when(decoratedAsyncHandler.onStatusReceived(mockResponseStatus)).thenReturn(State.CONTINUE); + + ResumableAsyncHandler handler = new ResumableAsyncHandler(decoratedAsyncHandler); + + State state = handler.onStatusReceived(mockResponseStatus); + verify(decoratedAsyncHandler).onStatusReceived(mockResponseStatus); + assertEquals(State.CONTINUE, state, "State returned should be equal to the one returned from decoratedAsyncHandler"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOnStatusReceived500Status() throws Exception { + MapResumableProcessor processor = new MapResumableProcessor(); + ResumableAsyncHandler handler = new ResumableAsyncHandler(processor); + HttpResponseStatus mockResponseStatus = mock(HttpResponseStatus.class); + when(mockResponseStatus.getStatusCode()).thenReturn(500); + when(mockResponseStatus.getUri()).thenReturn(mock(Uri.class)); + State state = handler.onStatusReceived(mockResponseStatus); + assertEquals(AsyncHandler.State.ABORT, state, "State should be ABORT for Internal Server Error status"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOnBodyPartReceived() throws Exception { + ResumableAsyncHandler handler = new ResumableAsyncHandler(); + HttpResponseBodyPart bodyPart = mock(HttpResponseBodyPart.class); + when(bodyPart.getBodyPartBytes()).thenReturn(T); + ByteBuffer buffer = ByteBuffer.allocate(0); + when(bodyPart.getBodyByteBuffer()).thenReturn(buffer); + State state = handler.onBodyPartReceived(bodyPart); + assertEquals(AsyncHandler.State.CONTINUE, state, "State should be CONTINUE for a successful onBodyPartReceived"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOnBodyPartReceivedWithResumableListenerThrowsException() throws Exception { + ResumableAsyncHandler handler = new ResumableAsyncHandler(); + + ResumableListener resumableListener = mock(ResumableListener.class); + doThrow(new IOException()).when(resumableListener).onBytesReceived(any()); + handler.setResumableListener(resumableListener); + + HttpResponseBodyPart bodyPart = mock(HttpResponseBodyPart.class); + State state = handler.onBodyPartReceived(bodyPart); + assertEquals(AsyncHandler.State.ABORT, state, + "State should be ABORT if the resumableListener threw an exception in onBodyPartReceived"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOnBodyPartReceivedWithDecoratedAsyncHandler() throws Exception { + HttpResponseBodyPart bodyPart = mock(HttpResponseBodyPart.class); + when(bodyPart.getBodyPartBytes()).thenReturn(new byte[0]); + ByteBuffer buffer = ByteBuffer.allocate(0); + when(bodyPart.getBodyByteBuffer()).thenReturn(buffer); + + @SuppressWarnings("unchecked") + AsyncHandler decoratedAsyncHandler = mock(AsyncHandler.class); + when(decoratedAsyncHandler.onBodyPartReceived(bodyPart)).thenReturn(State.CONTINUE); + + // following is needed to set the url variable + HttpResponseStatus mockResponseStatus = mock(HttpResponseStatus.class); + when(mockResponseStatus.getStatusCode()).thenReturn(200); + Uri uri = Uri.create("http://non.null/"); + when(mockResponseStatus.getUri()).thenReturn(uri); + + ResumableAsyncHandler handler = new ResumableAsyncHandler(decoratedAsyncHandler); + handler.onStatusReceived(mockResponseStatus); + + State state = handler.onBodyPartReceived(bodyPart); + assertEquals(State.CONTINUE, state, "State should be equal to the state returned from decoratedAsyncHandler"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOnHeadersReceived() throws Exception { + ResumableAsyncHandler handler = new ResumableAsyncHandler(); + HttpHeaders responseHeaders = new DefaultHttpHeaders(); + State status = handler.onHeadersReceived(responseHeaders); + assertEquals(AsyncHandler.State.CONTINUE, status, "State should be CONTINUE for a successful onHeadersReceived"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOnHeadersReceivedWithDecoratedAsyncHandler() throws Exception { + HttpHeaders responseHeaders = new DefaultHttpHeaders(); + + @SuppressWarnings("unchecked") + AsyncHandler decoratedAsyncHandler = mock(AsyncHandler.class); + when(decoratedAsyncHandler.onHeadersReceived(responseHeaders)).thenReturn(State.CONTINUE); + + ResumableAsyncHandler handler = new ResumableAsyncHandler(decoratedAsyncHandler); + State status = handler.onHeadersReceived(responseHeaders); + assertEquals(State.CONTINUE, status, "State should be equal to the state returned from decoratedAsyncHandler"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOnHeadersReceivedContentLengthMinus() throws Exception { + ResumableAsyncHandler handler = new ResumableAsyncHandler(); + HttpHeaders responseHeaders = new DefaultHttpHeaders(); + responseHeaders.add(CONTENT_LENGTH, -1); + State status = handler.onHeadersReceived(responseHeaders); + assertEquals(AsyncHandler.State.ABORT, status, "State should be ABORT for content length -1"); + } +} diff --git a/client/src/test/java/org/asynchttpclient/handler/resumable/ResumableRandomAccessFileListenerTest.java b/client/src/test/java/org/asynchttpclient/handler/resumable/ResumableRandomAccessFileListenerTest.java new file mode 100644 index 0000000000..b8a176b605 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/handler/resumable/ResumableRandomAccessFileListenerTest.java @@ -0,0 +1,51 @@ +/* + * 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.handler.resumable; + +import io.github.artsok.RepeatedIfExceptionsTest; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class ResumableRandomAccessFileListenerTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOnBytesReceivedBufferHasArray() throws IOException { + RandomAccessFile file = mock(RandomAccessFile.class); + ResumableRandomAccessFileListener listener = new ResumableRandomAccessFileListener(file); + byte[] array = {1, 2, 23, 33}; + ByteBuffer buf = ByteBuffer.wrap(array); + listener.onBytesReceived(buf); + verify(file).write(array, 0, 4); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testOnBytesReceivedBufferHasNoArray() throws IOException { + RandomAccessFile file = mock(RandomAccessFile.class); + ResumableRandomAccessFileListener listener = new ResumableRandomAccessFileListener(file); + + byte[] byteArray = {1, 2, 23, 33}; + ByteBuffer buf = ByteBuffer.allocateDirect(4); + buf.put(byteArray); + buf.flip(); + listener.onBytesReceived(buf); + verify(file).write(byteArray); + } +} diff --git a/client/src/test/java/org/asynchttpclient/netty/EventPipelineTest.java b/client/src/test/java/org/asynchttpclient/netty/EventPipelineTest.java new file mode 100644 index 0000000000..2a95230368 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/netty/EventPipelineTest.java @@ -0,0 +1,72 @@ +/* + * 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.netty; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.http.HttpMessage; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.Response; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.get; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class EventPipelineTest extends AbstractBasicTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void asyncPipelineTest() throws Exception { + Consumer httpAdditionalPipelineInitializer = channel -> channel.pipeline() + .addBefore("inflater", "copyEncodingHeader", new CopyEncodingHandler()); + + try (AsyncHttpClient client = asyncHttpClient(config().setHttpAdditionalChannelInitializer(httpAdditionalPipelineInitializer))) { + final CountDownLatch latch = new CountDownLatch(1); + client.executeRequest(get(getTargetUrl()), new AsyncCompletionHandlerAdapter() { + @Override + public Response onCompleted(Response response) { + try { + assertEquals(200, response.getStatusCode()); + assertEquals("", response.getHeader("X-Original-Content-Encoding")); + } finally { + latch.countDown(); + } + return response; + } + }).get(); + assertTrue(latch.await(TIMEOUT, TimeUnit.SECONDS)); + } + } + + private static class CopyEncodingHandler extends ChannelInboundHandlerAdapter { + @Override + public void channelRead(ChannelHandlerContext ctx, Object e) { + if (e instanceof HttpMessage) { + HttpMessage m = (HttpMessage) e; + // for test there is no Content-Encoding header so just hard + // coding value + // for verification + m.headers().set("X-Original-Content-Encoding", ""); + } + ctx.fireChannelRead(e); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java b/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java new file mode 100644 index 0000000000..5172bae7af --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java @@ -0,0 +1,93 @@ +/* + * 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.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; + +import static io.netty.handler.codec.http.HttpHeaderNames.SET_COOKIE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class NettyAsyncResponseTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void testCookieParseExpires() { + // e.g. "Tue, 27 Oct 2015 12:54:24 GMT"; + SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); + sdf.setTimeZone(TimeZone.getTimeZone("GMT")); + + Date date = new Date(System.currentTimeMillis() + 60000); + final String cookieDef = String.format("efmembercheck=true; expires=%s; path=/; domain=.eclipse.org", sdf.format(date)); + + HttpHeaders responseHeaders = new DefaultHttpHeaders().add(SET_COOKIE, cookieDef); + NettyResponse response = new NettyResponse(new NettyResponseStatus(null, null, null), responseHeaders, null); + + List cookies = response.getCookies(); + assertEquals(1, cookies.size()); + + Cookie cookie = cookies.get(0); + assertTrue(cookie.maxAge() >= 58 && cookie.maxAge() <= 60); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testCookieParseMaxAge() { + final String cookieDef = "efmembercheck=true; max-age=60; path=/; domain=.eclipse.org"; + + HttpHeaders responseHeaders = new DefaultHttpHeaders().add(SET_COOKIE, cookieDef); + NettyResponse response = new NettyResponse(new NettyResponseStatus(null, null, null), responseHeaders, null); + List cookies = response.getCookies(); + assertEquals(1, cookies.size()); + + Cookie cookie = cookies.get(0); + assertEquals(60, cookie.maxAge()); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testCookieParseWeirdExpiresValue() { + final String cookieDef = "efmembercheck=true; expires=60; path=/; domain=.eclipse.org"; + HttpHeaders responseHeaders = new DefaultHttpHeaders().add(SET_COOKIE, cookieDef); + NettyResponse response = new NettyResponse(new NettyResponseStatus(null, null, null), responseHeaders, null); + + List cookies = response.getCookies(); + assertEquals(1, cookies.size()); + + 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 new file mode 100644 index 0000000000..484b074a3c --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/netty/NettyConnectionResetByPeerTest.java @@ -0,0 +1,115 @@ +/* + * 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.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; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.time.Duration; +import java.util.Arrays; +import java.util.concurrent.Exchanger; +import java.util.function.Consumer; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +@ExtendWith(NettyLeakDetectorExtension.class) +public class NettyConnectionResetByPeerTest { + + private String resettingServerAddress; + + @BeforeEach + public void setUp() { + resettingServerAddress = createResettingServer(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testAsyncHttpClientConnectionResetByPeer() throws InterruptedException { + DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder() + .setRequestTimeout(Duration.ofMillis(1500)) + .build(); + try (AsyncHttpClient asyncHttpClient = new DefaultAsyncHttpClient(config)) { + asyncHttpClient.executeRequest(new RequestBuilder("GET").setUrl(resettingServerAddress)).get(); + } catch (Exception ex) { + assertInstanceOf(Exception.class, ex); + } + } + + private static String createResettingServer() { + return createServer(sock -> { + try (Socket socket = sock) { + socket.setSoLinger(true, 0); + InputStream inputStream = socket.getInputStream(); + //to not eliminate read + OutputStream os = new OutputStream() { + @Override + public void write(int b) { + // Do nothing + } + }; + os.write(startRead(inputStream)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + private static String createServer(Consumer handler) { + Exchanger portHolder = new Exchanger<>(); + Thread t = new Thread(() -> { + try (ServerSocket ss = new ServerSocket(0)) { + portHolder.exchange(ss.getLocalPort()); + while (true) { + handler.accept(ss.accept()); + } + } catch (Exception e) { + if (e instanceof InterruptedException) { + Thread.currentThread() + .interrupt(); + } + throw new RuntimeException(e); + } + }); + t.setDaemon(true); + t.start(); + return tryGetAddress(portHolder); + } + + private static String tryGetAddress(Exchanger portHolder) { + try { + return "http://localhost/" + portHolder.exchange(0); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + } + + private static byte[] startRead(InputStream inputStream) throws IOException { + byte[] buffer = new byte[4]; + int length = inputStream.read(buffer); + return Arrays.copyOf(buffer, length); + } +} diff --git a/client/src/test/java/org/asynchttpclient/netty/NettyRequestThrottleTimeoutTest.java b/client/src/test/java/org/asynchttpclient/netty/NettyRequestThrottleTimeoutTest.java new file mode 100644 index 0000000000..eade766201 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/netty/NettyRequestThrottleTimeoutTest.java @@ -0,0 +1,131 @@ +/* + * 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.netty; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncCompletionHandler; +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.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class NettyRequestThrottleTimeoutTest extends AbstractBasicTest { + private static final String MSG = "Enough is enough."; + private static final int SLEEPTIME_MS = 1000; + + @Override + public AbstractHandler configureHandler() throws Exception { + return new SlowHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRequestTimeout() throws IOException { + final Semaphore requestThrottle = new Semaphore(1); + final int samples = 10; + + try (AsyncHttpClient client = asyncHttpClient(config().setMaxConnections(1))) { + final CountDownLatch latch = new CountDownLatch(samples); + final List tooManyConnections = Collections.synchronizedList(new ArrayList<>(2)); + + for (int i = 0; i < samples; i++) { + new Thread(() -> { + try { + requestThrottle.acquire(); + Future responseFuture = null; + try { + responseFuture = client.prepareGet(getTargetUrl()).setRequestTimeout(Duration.ofMillis(SLEEPTIME_MS / 2)) + .execute(new AsyncCompletionHandler() { + + @Override + public Response onCompleted(Response response) { + return response; + } + + @Override + public void onThrowable(Throwable t) { + logger.error("onThrowable got an error", t); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // + } + requestThrottle.release(); + } + }); + } catch (Exception e) { + tooManyConnections.add(e); + } + + if (responseFuture != null) { + responseFuture.get(); + } + } catch (Exception e) { + // + } finally { + latch.countDown(); + } + }).start(); + } + + assertDoesNotThrow(() -> { + assertTrue(latch.await(30, TimeUnit.SECONDS)); + }); + + for (Exception e : tooManyConnections) { + logger.error("Exception while calling execute", e); + } + + assertTrue(tooManyConnections.isEmpty(), "Should not have any connection errors where too many connections have been attempted"); + } + } + + private static class SlowHandler extends AbstractHandler { + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException { + response.setStatus(HttpServletResponse.SC_OK); + final AsyncContext asyncContext = request.startAsync(); + new Thread(() -> { + try { + Thread.sleep(SLEEPTIME_MS); + response.getOutputStream().print(MSG); + response.getOutputStream().flush(); + asyncContext.complete(); + } catch (InterruptedException | IOException e) { + logger.error(e.getMessage(), e); + } + }).start(); + baseRequest.setHandled(true); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/netty/NettyResponseFutureTest.java b/client/src/test/java/org/asynchttpclient/netty/NettyResponseFutureTest.java new file mode 100644 index 0000000000..a052893002 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/netty/NettyResponseFutureTest.java @@ -0,0 +1,93 @@ +/* + * 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; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.AsyncHandler; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class NettyResponseFutureTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void testCancel() { + AsyncHandler asyncHandler = mock(AsyncHandler.class); + NettyResponseFuture nettyResponseFuture = new NettyResponseFuture<>(null, asyncHandler, null, 3, null, null, null); + boolean result = nettyResponseFuture.cancel(false); + verify(asyncHandler).onThrowable(any()); + assertTrue(result, "Cancel should return true if the Future was cancelled successfully"); + assertTrue(nettyResponseFuture.isCancelled(), "isCancelled should return true for a cancelled Future"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testCancelOnAlreadyCancelled() { + AsyncHandler asyncHandler = mock(AsyncHandler.class); + NettyResponseFuture nettyResponseFuture = new NettyResponseFuture<>(null, asyncHandler, null, 3, null, null, null); + nettyResponseFuture.cancel(false); + boolean result = nettyResponseFuture.cancel(false); + assertFalse(result, "cancel should return false for an already cancelled Future"); + assertTrue(nettyResponseFuture.isCancelled(), "isCancelled should return true for a cancelled Future"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetContentThrowsCancellationExceptionIfCancelled() throws Exception { + AsyncHandler asyncHandler = mock(AsyncHandler.class); + NettyResponseFuture nettyResponseFuture = new NettyResponseFuture<>(null, asyncHandler, null, 3, null, null, null); + nettyResponseFuture.cancel(false); + assertThrows(CancellationException.class, () -> nettyResponseFuture.get(), "A CancellationException must have occurred by now as 'cancel' was called before 'get'"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGet() throws Exception { + @SuppressWarnings("unchecked") + AsyncHandler asyncHandler = mock(AsyncHandler.class); + Object value = new Object(); + when(asyncHandler.onCompleted()).thenReturn(value); + NettyResponseFuture nettyResponseFuture = new NettyResponseFuture<>(null, asyncHandler, null, 3, null, null, null); + nettyResponseFuture.done(); + Object result = nettyResponseFuture.get(); + assertEquals(value, result, "The Future should return the value given by asyncHandler#onCompleted"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetThrowsExceptionThrownByAsyncHandler() throws Exception { + AsyncHandler asyncHandler = mock(AsyncHandler.class); + when(asyncHandler.onCompleted()).thenThrow(new RuntimeException()); + NettyResponseFuture nettyResponseFuture = new NettyResponseFuture<>(null, asyncHandler, null, 3, null, null, null); + nettyResponseFuture.done(); + assertThrows(ExecutionException.class, () -> nettyResponseFuture.get(), + "An ExecutionException must have occurred by now as asyncHandler threw an exception in 'onCompleted'"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetThrowsExceptionOnAbort() throws Exception { + AsyncHandler asyncHandler = mock(AsyncHandler.class); + NettyResponseFuture nettyResponseFuture = new NettyResponseFuture<>(null, asyncHandler, null, 3, null, null, null); + nettyResponseFuture.abort(new RuntimeException()); + assertThrows(ExecutionException.class, () -> nettyResponseFuture.get(), + "An ExecutionException must have occurred by now as 'abort' was called before 'get'"); + } +} 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 0000000000..f80c0911e6 --- /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/netty/RetryNonBlockingIssueTest.java b/client/src/test/java/org/asynchttpclient/netty/RetryNonBlockingIssueTest.java new file mode 100644 index 0000000000..60313166a1 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/netty/RetryNonBlockingIssueTest.java @@ -0,0 +1,209 @@ +/* + * 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.netty; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.HttpHeaders; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +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.ListenableFuture; +import org.asynchttpclient.RequestBuilder; +import org.asynchttpclient.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.get; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.junit.jupiter.api.Assertions.assertEquals; + +//FIXME there's no retry actually +public class RetryNonBlockingIssueTest extends AbstractBasicTest { + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector = addHttpConnector(server); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + context.addServlet(new ServletHolder(new MockExceptionServlet()), "/*"); + server.setHandler(context); + + server.start(); + port1 = connector.getLocalPort(); + } + + @Override + protected String getTargetUrl() { + return String.format("http://localhost:%d/", port1); + } + + private ListenableFuture testMethodRequest(AsyncHttpClient client, int requests, String action, String id) { + RequestBuilder r = get(getTargetUrl()) + .addQueryParam(action, "1") + .addQueryParam("maxRequests", String.valueOf(requests)) + .addQueryParam("id", id); + return client.executeRequest(r); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRetryNonBlocking() throws Exception { + AsyncHttpClientConfig config = config() + .setKeepAlive(true) + .setMaxConnections(100) + .setConnectTimeout(Duration.ofMinutes(1)) + .setRequestTimeout(Duration.ofSeconds(30)) + .build(); + + try (AsyncHttpClient client = asyncHttpClient(config)) { + List> res = new ArrayList<>(); + for (int i = 0; i < 32; i++) { + res.add(testMethodRequest(client, 3, "servlet", UUID.randomUUID().toString())); + } + + StringBuilder b = new StringBuilder(); + for (ListenableFuture r : res) { + Response theres = r.get(); + assertEquals(200, theres.getStatusCode()); + b.append("==============\r\n").append("Response Headers\r\n"); + HttpHeaders heads = theres.getHeaders(); + b.append(heads).append("\r\n").append("==============\r\n"); + } + System.out.println(b); + System.out.flush(); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRetryNonBlockingAsyncConnect() throws Exception { + AsyncHttpClientConfig config = config() + .setKeepAlive(true) + .setMaxConnections(100) + .setConnectTimeout(Duration.ofMinutes(1)) + .setRequestTimeout(Duration.ofSeconds(30)) + .build(); + + try (AsyncHttpClient client = asyncHttpClient(config)) { + List> res = new ArrayList<>(); + for (int i = 0; i < 32; i++) { + res.add(testMethodRequest(client, 3, "servlet", UUID.randomUUID().toString())); + } + + StringBuilder b = new StringBuilder(); + for (ListenableFuture r : res) { + Response theres = r.get(); + assertEquals(theres.getStatusCode(), 200); + b.append("==============\r\n").append("Response Headers\r\n"); + HttpHeaders heads = theres.getHeaders(); + b.append(heads).append("\r\n").append("==============\r\n"); + } + System.out.println(b); + System.out.flush(); + } + } + + @SuppressWarnings("serial") + public static class MockExceptionServlet extends HttpServlet { + + private final Map requests = new ConcurrentHashMap<>(); + + private synchronized int increment(String id) { + int val; + if (requests.containsKey(id)) { + Integer i = requests.get(id); + val = i + 1; + requests.put(id, val); + } else { + requests.put(id, 1); + val = 1; + } + System.out.println("REQUESTS: " + requests); + return val; + } + + @Override + public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + String maxRequests = req.getParameter("maxRequests"); + int max; + try { + max = Integer.parseInt(maxRequests); + } catch (NumberFormatException e) { + max = 3; + } + + String id = req.getParameter("id"); + int requestNo = increment(id); + String servlet = req.getParameter("servlet"); + String io = req.getParameter("io"); + String error = req.getParameter("500"); + + if (requestNo >= max) { + res.setHeader("Success-On-Attempt", String.valueOf(requestNo)); + res.setHeader("id", id); + + if (servlet != null && servlet.trim().length() > 0) { + res.setHeader("type", "servlet"); + } + + if (error != null && error.trim().length() > 0) { + res.setHeader("type", "500"); + } + + if (io != null && io.trim().length() > 0) { + res.setHeader("type", "io"); + } + res.setStatus(200); + res.setContentLength(0); + res.flushBuffer(); + return; + } + + res.setStatus(200); + res.setContentLength(100); + res.setContentType("application/octet-stream"); + res.flushBuffer(); + + // error after flushing the status + if (servlet != null && servlet.trim().length() > 0) { + throw new ServletException("Servlet Exception"); + } + + if (io != null && io.trim().length() > 0) { + throw new IOException("IO Exception"); + } + + if (error != null && error.trim().length() > 0) { + res.sendError(500, "servlet process was 500"); + } + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/netty/TimeToLiveIssueTest.java b/client/src/test/java/org/asynchttpclient/netty/TimeToLiveIssueTest.java new file mode 100644 index 0000000000..a2916248d7 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/netty/TimeToLiveIssueTest.java @@ -0,0 +1,58 @@ +/* + * 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.netty; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.Request; +import org.asynchttpclient.RequestBuilder; +import org.asynchttpclient.Response; +import org.junit.jupiter.api.Disabled; + +import java.time.Duration; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; + +public class TimeToLiveIssueTest extends AbstractBasicTest { + + @Disabled("https://github.com/AsyncHttpClient/async-http-client/issues/1113") + @RepeatedIfExceptionsTest(repeats = 5) + public void testTTLBug() throws Throwable { + // The purpose of this test is to reproduce two issues: + // 1) Connections that are rejected by the pool are not closed and eventually use all available sockets. + // 2) It is possible for a connection to be closed while active by the timer task that checks for expired connections. + + try (AsyncHttpClient client = asyncHttpClient(config() + .setKeepAlive(true) + .setConnectionTtl(Duration.ofMillis(1)) + .setPooledConnectionIdleTimeout(Duration.ofMillis(1)))) { + + for (int i = 0; i < 200000; ++i) { + Request request = new RequestBuilder().setUrl(String.format("http://localhost:%d/", port1)).build(); + + Future future = client.executeRequest(request); + future.get(5, TimeUnit.SECONDS); + + // This is to give a chance to the timer task that removes expired connection + // from sometimes winning over poll for the ownership of a connection. + if (System.currentTimeMillis() % 100 == 0) { + Thread.sleep(5); + } + } + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/netty/channel/SemaphoreRunner.java b/client/src/test/java/org/asynchttpclient/netty/channel/SemaphoreRunner.java new file mode 100644 index 0000000000..08fbb3464d --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/netty/channel/SemaphoreRunner.java @@ -0,0 +1,67 @@ +/* + * 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.netty.channel; + +class SemaphoreRunner { + + final ConnectionSemaphore semaphore; + final Thread acquireThread; + + volatile long acquireTime; + volatile Exception acquireException; + + SemaphoreRunner(ConnectionSemaphore semaphore, Object partitionKey) { + this.semaphore = semaphore; + acquireThread = new Thread(() -> { + long beforeAcquire = System.currentTimeMillis(); + try { + semaphore.acquireChannelLock(partitionKey); + } catch (Exception e) { + acquireException = e; + } finally { + acquireTime = System.currentTimeMillis() - beforeAcquire; + } + }); + } + + public void acquire() { + acquireThread.start(); + } + + public void interrupt() { + acquireThread.interrupt(); + } + + public void await() { + try { + acquireThread.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public boolean finished() { + return !acquireThread.isAlive(); + } + + public long getAcquireTime() { + return acquireTime; + } + + public Exception getAcquireException() { + return acquireException; + } +} diff --git a/client/src/test/java/org/asynchttpclient/netty/channel/SemaphoreTest.java b/client/src/test/java/org/asynchttpclient/netty/channel/SemaphoreTest.java new file mode 100644 index 0000000000..1c9f1db1d4 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/netty/channel/SemaphoreTest.java @@ -0,0 +1,174 @@ +/* + * 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.netty.channel; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.exception.TooManyConnectionsException; +import org.asynchttpclient.exception.TooManyConnectionsPerHostException; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class SemaphoreTest { + + static final int CHECK_ACQUIRE_TIME__PERMITS = 10; + static final int CHECK_ACQUIRE_TIME__TIMEOUT = 100; + + static final int NON_DETERMINISTIC__INVOCATION_COUNT = 10; + static final int NON_DETERMINISTIC__SUCCESS_PERCENT = 70; + + private final Object PK = new Object(); + + public Object[][] permitsAndRunnersCount() { + Object[][] objects = new Object[100][]; + int row = 0; + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + objects[row++] = new Object[]{i, j}; + } + } + return objects; + } + + @ParameterizedTest + @MethodSource("permitsAndRunnersCount") + @Timeout(unit = TimeUnit.MILLISECONDS, value = 1000) + public void maxConnectionCheckPermitCount(int permitCount, int runnerCount) { + allSemaphoresCheckPermitCount(new MaxConnectionSemaphore(permitCount, 0), permitCount, runnerCount); + } + + @ParameterizedTest + @MethodSource("permitsAndRunnersCount") + @Timeout(unit = TimeUnit.MILLISECONDS, value = 1000) + public void perHostCheckPermitCount(int permitCount, int runnerCount) { + allSemaphoresCheckPermitCount(new PerHostConnectionSemaphore(permitCount, 0), permitCount, runnerCount); + } + + @ParameterizedTest + @MethodSource("permitsAndRunnersCount") + @Timeout(unit = TimeUnit.MILLISECONDS, value = 1000) + public void combinedCheckPermitCount(int permitCount, int runnerCount) { + allSemaphoresCheckPermitCount(new CombinedConnectionSemaphore(permitCount, permitCount, 0), permitCount, runnerCount); + allSemaphoresCheckPermitCount(new CombinedConnectionSemaphore(0, permitCount, 0), permitCount, runnerCount); + allSemaphoresCheckPermitCount(new CombinedConnectionSemaphore(permitCount, 0, 0), permitCount, runnerCount); + } + + private void allSemaphoresCheckPermitCount(ConnectionSemaphore semaphore, int permitCount, int runnerCount) { + List runners = IntStream.range(0, runnerCount) + .mapToObj(i -> new SemaphoreRunner(semaphore, PK)) + .collect(Collectors.toList()); + runners.forEach(SemaphoreRunner::acquire); + runners.forEach(SemaphoreRunner::await); + + long tooManyConnectionsCount = runners.stream().map(SemaphoreRunner::getAcquireException) + .filter(Objects::nonNull) + .filter(e -> e instanceof IOException) + .count(); + + long acquired = runners.stream().map(SemaphoreRunner::getAcquireException) + .filter(Objects::isNull) + .count(); + + int expectedAcquired = permitCount > 0 ? Math.min(permitCount, runnerCount) : runnerCount; + + assertEquals(expectedAcquired, acquired); + assertEquals(runnerCount - acquired, tooManyConnectionsCount); + } + + @RepeatedTest(NON_DETERMINISTIC__INVOCATION_COUNT) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 1000) + public void maxConnectionCheckAcquireTime() { + checkAcquireTime(new MaxConnectionSemaphore(CHECK_ACQUIRE_TIME__PERMITS, CHECK_ACQUIRE_TIME__TIMEOUT)); + } + + @RepeatedTest(NON_DETERMINISTIC__INVOCATION_COUNT) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 1000) + public void perHostCheckAcquireTime() { + checkAcquireTime(new PerHostConnectionSemaphore(CHECK_ACQUIRE_TIME__PERMITS, CHECK_ACQUIRE_TIME__TIMEOUT)); + } + + @RepeatedTest(NON_DETERMINISTIC__INVOCATION_COUNT) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 1000) + public void combinedCheckAcquireTime() { + checkAcquireTime(new CombinedConnectionSemaphore(CHECK_ACQUIRE_TIME__PERMITS, + CHECK_ACQUIRE_TIME__PERMITS, + CHECK_ACQUIRE_TIME__TIMEOUT)); + } + + private void checkAcquireTime(ConnectionSemaphore semaphore) { + List runners = IntStream.range(0, CHECK_ACQUIRE_TIME__PERMITS * 2) + .mapToObj(i -> new SemaphoreRunner(semaphore, PK)) + .collect(Collectors.toList()); + long acquireStartTime = System.currentTimeMillis(); + runners.forEach(SemaphoreRunner::acquire); + runners.forEach(SemaphoreRunner::await); + long timeToAcquire = System.currentTimeMillis() - acquireStartTime; + + assertTrue(timeToAcquire >= CHECK_ACQUIRE_TIME__TIMEOUT - 50, "Semaphore acquired too soon: " + timeToAcquire + " ms"); //Lower Bound + assertTrue(timeToAcquire <= CHECK_ACQUIRE_TIME__TIMEOUT + 300, "Semaphore acquired too late: " + timeToAcquire + " ms"); //Upper Bound + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 1000) + public void maxConnectionCheckRelease() throws IOException { + checkRelease(new MaxConnectionSemaphore(1, 0)); + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 1000) + public void perHostCheckRelease() throws IOException { + checkRelease(new PerHostConnectionSemaphore(1, 0)); + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 1000) + public void combinedCheckRelease() throws IOException { + checkRelease(new CombinedConnectionSemaphore(1, 1, 0)); + } + + private void checkRelease(ConnectionSemaphore semaphore) throws IOException { + semaphore.acquireChannelLock(PK); + boolean tooManyCaught = false; + try { + semaphore.acquireChannelLock(PK); + } catch (TooManyConnectionsException | TooManyConnectionsPerHostException e) { + tooManyCaught = true; + } + assertTrue(tooManyCaught); + tooManyCaught = false; + semaphore.releaseChannelLock(PK); + try { + semaphore.acquireChannelLock(PK); + } catch (TooManyConnectionsException | TooManyConnectionsPerHostException e) { + tooManyCaught = true; + } + assertFalse(tooManyCaught); + } +} diff --git a/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java b/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java new file mode 100644 index 0000000000..0bce17d4c7 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java @@ -0,0 +1,236 @@ +/* + * 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. + * 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.ntlm; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.io.output.ByteArrayOutputStream; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.Realm; +import org.asynchttpclient.Response; +import org.asynchttpclient.ntlm.NtlmEngine.Type2Message; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.get; +import static org.asynchttpclient.Dsl.ntlmAuthRealm; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class NtlmTest extends AbstractBasicTest { + + private static byte[] longToBytes(long x) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(x); + return buffer.array(); + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new NTLMHandler(); + } + + private static Realm.Builder realmBuilderBase() { + return ntlmAuthRealm("Zaphod", "Beeblebrox") + .setNtlmDomain("Ursa-Minor") + .setNtlmHost("LightCity"); + } + + private void ntlmAuthTest(Realm.Builder realmBuilder) throws IOException, InterruptedException, ExecutionException { + try (AsyncHttpClient client = asyncHttpClient(config().setRealm(realmBuilder))) { + Future responseFuture = client.executeRequest(get(getTargetUrl())); + int status = responseFuture.get().getStatusCode(); + assertEquals(200, status); + } + } + + @Test + public void testUnicodeLittleUnmarkedEncoding() { + final Charset unicodeLittleUnmarked = Charset.forName("UnicodeLittleUnmarked"); + final Charset utf16le = StandardCharsets.UTF_16LE; + assertEquals(unicodeLittleUnmarked, utf16le); + assertArrayEquals("Test @ テスト".getBytes(unicodeLittleUnmarked), "Test @ テスト".getBytes(utf16le)); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void lazyNTLMAuthTest() throws Exception { + ntlmAuthTest(realmBuilderBase()); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void preemptiveNTLMAuthTest() throws Exception { + ntlmAuthTest(realmBuilderBase().setUsePreemptiveAuth(true)); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGenerateType1Msg() { + NtlmEngine engine = new NtlmEngine(); + String message = engine.generateType1Msg(); + assertEquals(message, "TlRMTVNTUAABAAAAAYIIogAAAAAoAAAAAAAAACgAAAAFASgKAAAADw==", "Incorrect type1 message generated"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGenerateType3MsgThrowsExceptionWhenChallengeTooShort() { + NtlmEngine engine = new NtlmEngine(); + 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"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGenerateType3MsgThrowsExceptionWhenChallengeDoesNotFollowCorrectFormat() { + NtlmEngine engine = new NtlmEngine(); + 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"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGenerateType3MsgThworsExceptionWhenType2IndicatorNotPresent() throws IOException { + try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) { + buf.write("NTLMSSP".getBytes(StandardCharsets.US_ASCII)); + buf.write(0); + // type 2 indicator + buf.write(3); + buf.write(0); + buf.write(0); + buf.write(0); + buf.write("challenge".getBytes()); + NtlmEngine engine = new NtlmEngine(); + 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"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGenerateType3MsgThrowsExceptionWhenUnicodeSupportNotIndicated() throws IOException { + try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) { + buf.write("NTLMSSP".getBytes(StandardCharsets.US_ASCII)); + buf.write(0); + // type 2 indicator + buf.write(2); + buf.write(0); + buf.write(0); + buf.write(0); + + buf.write(longToBytes(1L)); // we want to write a Long + + // flags + buf.write(0);// unicode support indicator + buf.write(0); + buf.write(0); + buf.write(0); + + buf.write(longToBytes(1L));// challenge + NtlmEngine engine = new NtlmEngine(); + 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"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGenerateType2Msg() { + Type2Message type2Message = new Type2Message("TlRMTVNTUAACAAAAAAAAACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAA=="); + assertEquals(40, type2Message.getMessageLength(), "This is a sample challenge that should return 40"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGenerateType3Msg() throws IOException { + try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) { + buf.write("NTLMSSP".getBytes(StandardCharsets.US_ASCII)); + buf.write(0); + // type 2 indicator + buf.write(2); + buf.write(0); + buf.write(0); + buf.write(0); + + buf.write(longToBytes(0L)); // we want to write a Long + + // flags + buf.write(1);// unicode support indicator + buf.write(0); + buf.write(0); + buf.write(0); + + buf.write(longToBytes(1L));// challenge + NtlmEngine engine = new NtlmEngine(); + String type3Msg = NtlmEngine.generateType3Msg("username", "password", "localhost", "workstation", + Base64.getEncoder().encodeToString(buf.toByteArray())); + assertEquals(type3Msg, + "TlRMTVNTUAADAAAAGAAYAEgAAAAYABgAYAAAABIAEgB4AAAAEAAQAIoAAAAWABYAmgAAAAAAAACwAAAAAQAAAgUBKAoAAAAP1g6lqqN1HZ0wSSxeQ5riQkyh7/UexwVlCPQm0SHU2vsDQm2wM6NbT2zPonPzLJL0TABPAEMAQQBMAEgATwBTAFQAdQBzAGUAcgBuAGEAbQBlAFcATwBSAEsAUwBUAEEAVABJAE8ATgA=", + "Incorrect type3 message generated"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testWriteULong() { + // test different combinations so that different positions in the byte array will be written + byte[] buffer = new byte[4]; + NtlmEngine.writeULong(buffer, 1, 0); + assertArrayEquals(new byte[]{1, 0, 0, 0}, buffer, "Unsigned long value 1 was not written correctly to the buffer"); + + buffer = new byte[4]; + NtlmEngine.writeULong(buffer, 257, 0); + assertArrayEquals(new byte[]{1, 1, 0, 0}, buffer, "Unsigned long value 257 was not written correctly to the buffer"); + + buffer = new byte[4]; + NtlmEngine.writeULong(buffer, 16777216, 0); + assertArrayEquals(new byte[]{0, 0, 0, 1}, buffer, "Unsigned long value 16777216 was not written correctly to the buffer"); + } + + public static class NTLMHandler extends AbstractHandler { + + @Override + public void handle(String pathInContext, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + String authorization = httpRequest.getHeader("Authorization"); + if (authorization == null) { + httpResponse.setStatus(401); + httpResponse.setHeader("WWW-Authenticate", "NTLM"); + + } else if ("NTLM TlRMTVNTUAABAAAAAYIIogAAAAAoAAAAAAAAACgAAAAFASgKAAAADw==".equals(authorization)) { + httpResponse.setStatus(401); + httpResponse.setHeader("WWW-Authenticate", "NTLM TlRMTVNTUAACAAAAAAAAACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAA=="); + + } else if ("NTLM TlRMTVNTUAADAAAAGAAYAEgAAAAYABgAYAAAABQAFAB4AAAADAAMAIwAAAASABIAmAAAAAAAAACqAAAAAYIAAgUBKAoAAAAPrYfKbe/jRoW5xDxHeoxC1gBmfWiS5+iX4OAN4xBKG/IFPwfH3agtPEia6YnhsADTVQBSAFMAQQAtAE0ASQBOAE8AUgBaAGEAcABoAG8AZABMAEkARwBIAFQAQwBJAFQAWQA=" + .equals(authorization)) { + httpResponse.setStatus(200); + } else { + httpResponse.setStatus(401); + } + + httpResponse.setContentLength(0); + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/proxy/CustomHeaderProxyTest.java b/client/src/test/java/org/asynchttpclient/proxy/CustomHeaderProxyTest.java new file mode 100644 index 0000000000..3448bcae7e --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/proxy/CustomHeaderProxyTest.java @@ -0,0 +1,127 @@ +/* + * 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.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.Response; +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; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.post; +import static org.asynchttpclient.Dsl.proxyServer; +import static org.asynchttpclient.test.TestUtils.LARGE_IMAGE_BYTES; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.asynchttpclient.test.TestUtils.addHttpsConnector; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Proxy usage tests. + */ +public class CustomHeaderProxyTest extends AbstractBasicTest { + + private Server server2; + + private static final String customHeaderName = "Custom-Header"; + private static final String customHeaderValue = "Custom-Value"; + + @Override + public AbstractHandler configureHandler() throws Exception { + return new ProxyHandler(customHeaderName, customHeaderValue); + } + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector = addHttpConnector(server); + server.setHandler(configureHandler()); + server.start(); + port1 = connector.getLocalPort(); + + server2 = new Server(); + ServerConnector connector2 = addHttpsConnector(server2); + server2.setHandler(new EchoHandler()); + server2.start(); + port2 = connector2.getLocalPort(); + + logger.info("Local HTTP server started successfully"); + } + + @Override + @AfterEach + public void tearDownGlobal() throws Exception { + server.stop(); + server2.stop(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testHttpProxy() throws Exception { + AsyncHttpClientConfig config = config() + .setFollowRedirect(true) + .setProxyServer( + proxyServer("localhost", port1) + .setCustomHeaders(req -> new DefaultHttpHeaders().add(customHeaderName, customHeaderValue)) + .build() + ) + .setUseInsecureTrustManager(true) + .build(); + try (AsyncHttpClient asyncHttpClient = asyncHttpClient(config)) { + Response r = asyncHttpClient.executeRequest(post(getTargetUrl2()).setBody(new ByteArrayBodyGenerator(LARGE_IMAGE_BYTES))).get(); + assertEquals(200, r.getStatusCode()); + } + } + + public static class ProxyHandler extends ConnectHandler { + String customHeaderName; + String customHeaderValue; + + public ProxyHandler(String customHeaderName, String customHeaderValue) { + this.customHeaderName = customHeaderName; + this.customHeaderValue = customHeaderValue; + } + + @Override + public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + if (HttpConstants.Methods.CONNECT.equalsIgnoreCase(request.getMethod())) { + if (request.getHeader(customHeaderName).equals(customHeaderValue)) { + response.setStatus(HttpServletResponse.SC_OK); + super.handle(s, r, request, response); + } else { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + r.setHandled(true); + } + } else { + super.handle(s, r, request, response); + } + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/proxy/HttpsProxyTest.java b/client/src/test/java/org/asynchttpclient/proxy/HttpsProxyTest.java new file mode 100644 index 0000000000..9bd5ca911c --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/proxy/HttpsProxyTest.java @@ -0,0 +1,214 @@ +/* + * 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.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; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.get; +import static org.asynchttpclient.Dsl.post; +import static org.asynchttpclient.Dsl.proxyServer; +import static org.asynchttpclient.test.TestUtils.LARGE_IMAGE_BYTES; +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. + */ +public class HttpsProxyTest extends AbstractBasicTest { + + private Server server2; + + @Override + public AbstractHandler configureHandler() throws Exception { + return new ProxyHandler(); + } + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector = addHttpConnector(server); + server.setHandler(configureHandler()); + server.start(); + port1 = connector.getLocalPort(); + + server2 = new Server(); + ServerConnector connector2 = addHttpsConnector(server2); + server2.setHandler(new EchoHandler()); + server2.start(); + port2 = connector2.getLocalPort(); + + logger.info("Local HTTP server started successfully"); + } + + @Override + @AfterEach + public void tearDownGlobal() throws Exception { + server.stop(); + server2.stop(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRequestProxy() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().setFollowRedirect(true).setUseInsecureTrustManager(true))) { + RequestBuilder rb = get(getTargetUrl2()).setProxyServer(proxyServer("localhost", port1)); + Response response = client.executeRequest(rb.build()).get(); + assertEquals(200, response.getStatusCode()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testConfigProxy() throws Exception { + AsyncHttpClientConfig config = config() + .setFollowRedirect(true) + .setProxyServer(proxyServer("localhost", port1).build()) + .setUseInsecureTrustManager(true) + .build(); + + try (AsyncHttpClient client = asyncHttpClient(config)) { + Response response = client.executeRequest(get(getTargetUrl2())).get(); + assertEquals(200, response.getStatusCode()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testNoDirectRequestBodyWithProxy() throws Exception { + AsyncHttpClientConfig config = config() + .setFollowRedirect(true) + .setProxyServer(proxyServer("localhost", port1).build()) + .setUseInsecureTrustManager(true) + .build(); + + try (AsyncHttpClient client = asyncHttpClient(config)) { + Response response = client.executeRequest(post(getTargetUrl2()).setBody(new ByteArrayBodyGenerator(LARGE_IMAGE_BYTES))).get(); + assertEquals(200, response.getStatusCode()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDecompressBodyWithProxy() throws Exception { + AsyncHttpClientConfig config = config() + .setFollowRedirect(true) + .setProxyServer(proxyServer("localhost", port1).build()) + .setUseInsecureTrustManager(true) + .build(); + + try (AsyncHttpClient client = asyncHttpClient(config)) { + String body = "hello world"; + Response response = client.executeRequest(post(getTargetUrl2()) + .setHeader("X-COMPRESS", "true") + .setBody(body)).get(); + + assertEquals(200, response.getStatusCode()); + assertEquals(body, response.getResponseBody()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPooledConnectionsWithProxy() throws Exception { + try (AsyncHttpClient asyncHttpClient = asyncHttpClient(config().setFollowRedirect(true).setUseInsecureTrustManager(true).setKeepAlive(true))) { + RequestBuilder rb = get(getTargetUrl2()).setProxyServer(proxyServer("localhost", port1)); + + Response response1 = asyncHttpClient.executeRequest(rb.build()).get(); + assertEquals(200, response1.getStatusCode()); + + Response response2 = asyncHttpClient.executeRequest(rb.build()).get(); + 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/proxy/NTLMProxyTest.java b/client/src/test/java/org/asynchttpclient/proxy/NTLMProxyTest.java new file mode 100644 index 0000000000..d3e5b54c7d --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/proxy/NTLMProxyTest.java @@ -0,0 +1,108 @@ +/* + * 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.proxy; + +import io.github.artsok.RepeatedIfExceptionsTest; +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.Realm; +import org.asynchttpclient.Response; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.get; +import static org.asynchttpclient.Dsl.ntlmAuthRealm; +import static org.asynchttpclient.Dsl.proxyServer; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class NTLMProxyTest extends AbstractBasicTest { + + @Override + public AbstractHandler configureHandler() throws Exception { + return new NTLMProxyHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void ntlmProxyTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + org.asynchttpclient.Request request = get("http://localhost/").setProxyServer(ntlmProxy()).build(); + Future responseFuture = client.executeRequest(request); + int status = responseFuture.get().getStatusCode(); + assertEquals(200, status); + } + } + + private ProxyServer ntlmProxy() { + Realm realm = ntlmAuthRealm("Zaphod", "Beeblebrox") + .setNtlmDomain("Ursa-Minor") + .setNtlmHost("LightCity") + .build(); + return proxyServer("localhost", port2).setRealm(realm).build(); + } + + public static class NTLMProxyHandler extends AbstractHandler { + + private final AtomicInteger state = new AtomicInteger(); + + @Override + public void handle(String pathInContext, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + + String authorization = httpRequest.getHeader("Proxy-Authorization"); + boolean asExpected = false; + + switch (state.getAndIncrement()) { + case 0: + if (authorization == null) { + httpResponse.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407); + httpResponse.setHeader("Proxy-Authenticate", "NTLM"); + asExpected = true; + } + break; + case 1: + if ("NTLM TlRMTVNTUAABAAAAAYIIogAAAAAoAAAAAAAAACgAAAAFASgKAAAADw==".equals(authorization)) { + httpResponse.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407); + httpResponse.setHeader("Proxy-Authenticate", "NTLM TlRMTVNTUAACAAAAAAAAACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAA=="); + asExpected = true; + } + break; + case 2: + if ("NTLM TlRMTVNTUAADAAAAGAAYAEgAAAAYABgAYAAAABQAFAB4AAAADAAMAIwAAAASABIAmAAAAAAAAACqAAAAAYIAAgUBKAoAAAAPrYfKbe/jRoW5xDxHeoxC1gBmfWiS5+iX4OAN4xBKG/IFPwfH3agtPEia6YnhsADTVQBSAFMAQQAtAE0ASQBOAE8AUgBaAGEAcABoAG8AZABMAEkARwBIAFQAQwBJAFQAWQA=" + .equals(authorization)) { + httpResponse.setStatus(HttpStatus.OK_200); + asExpected = true; + } + break; + default: + } + + if (!asExpected) { + httpResponse.setStatus(HttpStatus.FORBIDDEN_403); + } + httpResponse.setContentLength(0); + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/proxy/ProxyTest.java b/client/src/test/java/org/asynchttpclient/proxy/ProxyTest.java new file mode 100644 index 0000000000..14da96360f --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/proxy/ProxyTest.java @@ -0,0 +1,355 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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.proxy; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaders; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncCompletionHandler; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.Request; +import org.asynchttpclient.Response; +import org.asynchttpclient.config.AsyncHttpClientConfigDefaults; +import org.asynchttpclient.config.AsyncHttpClientConfigHelper; +import org.asynchttpclient.testserver.SocksProxy; +import org.asynchttpclient.util.ProxyUtils; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.get; +import static org.asynchttpclient.Dsl.proxyServer; +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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Proxy usage tests. + * + * @author Hubert Iwaniuk + */ +public class ProxyTest extends AbstractBasicTest { + + @Override + public AbstractHandler configureHandler() throws Exception { + return new ProxyHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRequestLevelProxy() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + String target = "http://localhost:1234/"; + Future f = client.prepareGet(target).setProxyServer(proxyServer("localhost", port1)).execute(); + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(HttpServletResponse.SC_OK, resp.getStatusCode()); + assertEquals("/", resp.getHeader("target")); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void asyncDoPostProxyTest() throws Throwable { + try (AsyncHttpClient client = asyncHttpClient(config().setProxyServer(proxyServer("localhost", port2).build()))) { + HttpHeaders h = new DefaultHttpHeaders(); + h.add(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 5; i++) { + sb.append("param_").append(i).append("=value_").append(i).append('&'); + } + sb.setLength(sb.length() - 1); + + Response response = client.preparePost(getTargetUrl()) + .setHeaders(h) + .setBody(sb.toString()) + .execute(new AsyncCompletionHandler() { + @Override + public Response onCompleted(Response response) { + return response; + } + + @Override + public void onThrowable(Throwable t) { + } + }).get(); + + assertEquals(200, response.getStatusCode()); + assertEquals(APPLICATION_X_WWW_FORM_URLENCODED.toString(), response.getHeader("X-" + CONTENT_TYPE)); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGlobalProxy() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().setProxyServer(proxyServer("localhost", port1)))) { + String target = "http://localhost:1234/"; + Future f = client.prepareGet(target).execute(); + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getHeader("target"), "/"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testBothProxies() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().setProxyServer(proxyServer("localhost", port1 - 1)))) { + String target = "http://localhost:1234/"; + Future f = client.prepareGet(target).setProxyServer(proxyServer("localhost", port1)).execute(); + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getHeader("target"), "/"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testNonProxyHost() { + // // should avoid, it's in non-proxy hosts + Request req = get("http://somewhere.com/foo").build(); + ProxyServer proxyServer = proxyServer("localhost", 1234).setNonProxyHost("somewhere.com").build(); + assertTrue(proxyServer.isIgnoredForHost(req.getUri().getHost())); + // + // // should avoid, it's in non-proxy hosts (with "*") + req = get("http://sub.somewhere.com/foo").build(); + proxyServer = proxyServer("localhost", 1234).setNonProxyHost("*.somewhere.com").build(); + assertTrue(proxyServer.isIgnoredForHost(req.getUri().getHost())); + + // should use it + req = get("http://sub.somewhere.com/foo").build(); + proxyServer = proxyServer("localhost", 1234).setNonProxyHost("*.somewhere.com").build(); + assertTrue(proxyServer.isIgnoredForHost(req.getUri().getHost())); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testNonProxyHostsRequestOverridesConfig() throws Exception { + ProxyServer configProxy = proxyServer("localhost", port1 - 1).build(); + ProxyServer requestProxy = proxyServer("localhost", port1).setNonProxyHost("localhost").build(); + + try (AsyncHttpClient client = asyncHttpClient(config().setProxyServer(configProxy))) { + client.prepareGet("http://localhost:1234/").setProxyServer(requestProxy).execute().get(); + } catch (Exception ex) { + assertInstanceOf(ConnectException.class, ex.getCause()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRequestNonProxyHost() throws Exception { + ProxyServer proxy = proxyServer("localhost", port1 - 1).setNonProxyHost("localhost").build(); + try (AsyncHttpClient client = asyncHttpClient()) { + String target = "http://localhost/" + port1 + '/'; + Future f = client.prepareGet(target).setProxyServer(proxy).execute(); + Response resp = f.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getHeader("target"), "/"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void runSequentiallyBecauseNotThreadSafe() throws Exception { + testProxyProperties(); + testIgnoreProxyPropertiesByDefault(); + testProxyActivationProperty(); + testWildcardNonProxyHosts(); + testUseProxySelector(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testProxyProperties() throws IOException, ExecutionException, TimeoutException, InterruptedException { + // FIXME not threadsafe! + Properties originalProps = new Properties(); + originalProps.putAll(System.getProperties()); + System.setProperty(ProxyUtils.PROXY_HOST, "127.0.0.1"); + System.setProperty(ProxyUtils.PROXY_PORT, String.valueOf(port1)); + System.setProperty(ProxyUtils.PROXY_NONPROXYHOSTS, "localhost"); + AsyncHttpClientConfigHelper.reloadProperties(); + + try (AsyncHttpClient client = asyncHttpClient(config().setUseProxyProperties(true))) { + String proxifiedtarget = "http://127.0.0.1:1234/"; + Future responseFuture = client.prepareGet(proxifiedtarget).execute(); + Response resp = responseFuture.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getHeader("target"), "/"); + + String nonProxifiedtarget = "http://localhost:1234/"; + final Future secondResponseFuture = client.prepareGet(nonProxifiedtarget).execute(); + + assertThrows(Exception.class, () -> secondResponseFuture.get(3, TimeUnit.SECONDS)); + } finally { + System.setProperties(originalProps); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testIgnoreProxyPropertiesByDefault() throws IOException, TimeoutException, InterruptedException { + // FIXME not threadsafe! + Properties originalProps = new Properties(); + originalProps.putAll(System.getProperties()); + System.setProperty(ProxyUtils.PROXY_HOST, "localhost"); + System.setProperty(ProxyUtils.PROXY_PORT, String.valueOf(port1)); + System.setProperty(ProxyUtils.PROXY_NONPROXYHOSTS, "localhost"); + AsyncHttpClientConfigHelper.reloadProperties(); + + try (AsyncHttpClient client = asyncHttpClient()) { + String target = "http://localhost:1234/"; + final Future responseFuture = client.prepareGet(target).execute(); + assertThrows(Exception.class, () -> responseFuture.get(3, TimeUnit.SECONDS)); + } finally { + System.setProperties(originalProps); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testProxyActivationProperty() throws IOException, ExecutionException, TimeoutException, InterruptedException { + // FIXME not threadsafe! + Properties originalProps = new Properties(); + originalProps.putAll(System.getProperties()); + System.setProperty(ProxyUtils.PROXY_HOST, "127.0.0.1"); + System.setProperty(ProxyUtils.PROXY_PORT, String.valueOf(port1)); + System.setProperty(ProxyUtils.PROXY_NONPROXYHOSTS, "localhost"); + System.setProperty(AsyncHttpClientConfigDefaults.ASYNC_CLIENT_CONFIG_ROOT + "useProxyProperties", "true"); + AsyncHttpClientConfigHelper.reloadProperties(); + + try (AsyncHttpClient client = asyncHttpClient()) { + String proxifiedTarget = "http://127.0.0.1:1234/"; + Future responseFuture = client.prepareGet(proxifiedTarget).execute(); + Response resp = responseFuture.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getHeader("target"), "/"); + + String nonProxifiedTarget = "http://localhost:1234/"; + Future secondResponseFuture = client.prepareGet(nonProxifiedTarget).execute(); + assertThrows(Exception.class, () -> secondResponseFuture.get(3, TimeUnit.SECONDS)); + } finally { + System.setProperties(originalProps); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testWildcardNonProxyHosts() throws IOException, TimeoutException, InterruptedException { + // FIXME not threadsafe! + Properties originalProps = new Properties(); + originalProps.putAll(System.getProperties()); + System.setProperty(ProxyUtils.PROXY_HOST, "127.0.0.1"); + System.setProperty(ProxyUtils.PROXY_PORT, String.valueOf(port1)); + System.setProperty(ProxyUtils.PROXY_NONPROXYHOSTS, "127.*"); + AsyncHttpClientConfigHelper.reloadProperties(); + + try (AsyncHttpClient client = asyncHttpClient(config().setUseProxyProperties(true))) { + String nonProxifiedTarget = "http://127.0.0.1:1234/"; + Future secondResponseFuture = client.prepareGet(nonProxifiedTarget).execute(); + assertThrows(Exception.class, () -> secondResponseFuture.get(3, TimeUnit.SECONDS)); + } finally { + System.setProperties(originalProps); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testUseProxySelector() throws IOException, ExecutionException, TimeoutException, InterruptedException { + ProxySelector originalProxySelector = ProxySelector.getDefault(); + ProxySelector.setDefault(new ProxySelector() { + @Override + public List select(URI uri) { + if ("127.0.0.1".equals(uri.getHost())) { + return List.of(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", port1))); + } else { + return Collections.singletonList(Proxy.NO_PROXY); + } + } + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + // NO-OP + } + }); + + try (AsyncHttpClient client = asyncHttpClient(config().setUseProxySelector(true))) { + String proxifiedTarget = "http://127.0.0.1:1234/"; + Future responseFuture = client.prepareGet(proxifiedTarget).execute(); + Response resp = responseFuture.get(3, TimeUnit.SECONDS); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getHeader("target"), "/"); + + String nonProxifiedTarget = "http://localhost:1234/"; + Future secondResponseFuture = client.prepareGet(nonProxifiedTarget).execute(); + assertThrows(Exception.class, () -> secondResponseFuture.get(3, TimeUnit.SECONDS)); + } finally { + // FIXME not threadsafe + ProxySelector.setDefault(originalProxySelector); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void runSocksProxy() throws Exception { + new Thread(() -> { + try { + new SocksProxy(60000); + } catch (IOException e) { + logger.error("Failed to establish SocksProxy", e); + } + }).start(); + + try (AsyncHttpClient client = asyncHttpClient()) { + String target = "http://localhost/" + port1 + '/'; + Future f = client.prepareGet(target) + .setProxyServer(new ProxyServer.Builder("localhost", 8000).setProxyType(ProxyType.SOCKS_V4)) + .execute(); + + assertEquals(200, f.get(60, TimeUnit.SECONDS).getStatusCode()); + } + } + + public static class ProxyHandler extends AbstractHandler { + + @Override + public void handle(String s, org.eclipse.jetty.server.Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + if ("GET".equalsIgnoreCase(request.getMethod())) { + response.addHeader("target", r.getHttpURI().getPath()); + response.setStatus(HttpServletResponse.SC_OK); + } else if ("POST".equalsIgnoreCase(request.getMethod())) { + response.addHeader("X-" + CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED.toString()); + } else { + response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + } + r.setHandled(true); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/BodyChunkTest.java b/client/src/test/java/org/asynchttpclient/request/body/BodyChunkTest.java new file mode 100644 index 0000000000..b33eb382ed --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/BodyChunkTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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.request.body; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.RequestBuilder; +import org.asynchttpclient.Response; +import org.asynchttpclient.request.body.generator.InputStreamBodyGenerator; + +import java.io.ByteArrayInputStream; +import java.time.Duration; +import java.util.concurrent.Future; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.post; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BodyChunkTest extends AbstractBasicTest { + + private static final String MY_MESSAGE = "my message"; + + @RepeatedIfExceptionsTest(repeats = 5) + public void negativeContentTypeTest() throws Exception { + + AsyncHttpClientConfig config = config() + .setConnectTimeout(Duration.ofMillis(100)) + .setMaxConnections(50) + .setRequestTimeout(Duration.ofMinutes(5)) + .build(); + + try (AsyncHttpClient client = asyncHttpClient(config)) { + RequestBuilder requestBuilder = post(getTargetUrl()) + .setHeader("Content-Type", "message/rfc822") + .setBody(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes()))); + + Future future = client.executeRequest(requestBuilder.build()); + + System.out.println("waiting for response"); + Response response = future.get(); + assertEquals(200, response.getStatusCode()); + assertEquals(MY_MESSAGE, response.getResponseBody()); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/ChunkingTest.java b/client/src/test/java/org/asynchttpclient/request/body/ChunkingTest.java new file mode 100755 index 0000000000..8fc32e08d2 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/ChunkingTest.java @@ -0,0 +1,128 @@ +/* + * 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.Unpooled; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.DefaultAsyncHttpClientConfig; +import org.asynchttpclient.ListenableFuture; +import org.asynchttpclient.Request; +import org.asynchttpclient.Response; +import org.asynchttpclient.request.body.generator.FeedableBodyGenerator; +import org.asynchttpclient.request.body.generator.InputStreamBodyGenerator; +import org.asynchttpclient.request.body.generator.UnboundedQueueFeedableBodyGenerator; + +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.nio.file.Files; +import java.time.Duration; +import java.util.concurrent.ExecutionException; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.post; +import static org.asynchttpclient.test.TestUtils.LARGE_IMAGE_BYTES; +import static org.asynchttpclient.test.TestUtils.LARGE_IMAGE_FILE; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class ChunkingTest extends AbstractBasicTest { + + // So we can just test the returned data is the image, + // and doesn't contain the chunked delimiters. + @RepeatedIfExceptionsTest(repeats = 5) + public void testBufferLargerThanFileWithStreamBodyGenerator() throws Throwable { + doTestWithInputStreamBodyGenerator(new BufferedInputStream(Files.newInputStream(LARGE_IMAGE_FILE.toPath()), 400000)); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testBufferSmallThanFileWithStreamBodyGenerator() throws Throwable { + doTestWithInputStreamBodyGenerator(new BufferedInputStream(Files.newInputStream(LARGE_IMAGE_FILE.toPath()))); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDirectFileWithStreamBodyGenerator() throws Throwable { + doTestWithInputStreamBodyGenerator(Files.newInputStream(LARGE_IMAGE_FILE.toPath())); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDirectFileWithFeedableBodyGenerator() throws Throwable { + doTestWithFeedableBodyGenerator(Files.newInputStream(LARGE_IMAGE_FILE.toPath())); + } + + private void doTestWithInputStreamBodyGenerator(InputStream is) throws Throwable { + try { + try (AsyncHttpClient c = asyncHttpClient(httpClientBuilder())) { + ListenableFuture responseFuture = c.executeRequest(post(getTargetUrl()).setBody(new InputStreamBodyGenerator(is))); + waitForAndAssertResponse(responseFuture); + } + } finally { + is.close(); + } + } + + private void doTestWithFeedableBodyGenerator(InputStream is) throws Throwable { + try { + try (AsyncHttpClient c = asyncHttpClient(httpClientBuilder())) { + final FeedableBodyGenerator feedableBodyGenerator = new UnboundedQueueFeedableBodyGenerator(); + Request r = post(getTargetUrl()).setBody(feedableBodyGenerator).build(); + ListenableFuture responseFuture = c.executeRequest(r); + feed(feedableBodyGenerator, is); + waitForAndAssertResponse(responseFuture); + } + } finally { + is.close(); + } + } + + private static void feed(FeedableBodyGenerator feedableBodyGenerator, InputStream is) throws Exception { + try (InputStream inputStream = is) { + byte[] buffer = new byte[512]; + for (int i; (i = inputStream.read(buffer)) > -1; ) { + byte[] chunk = new byte[i]; + System.arraycopy(buffer, 0, chunk, 0, i); + feedableBodyGenerator.feed(Unpooled.wrappedBuffer(chunk), false); + } + } + feedableBodyGenerator.feed(Unpooled.EMPTY_BUFFER, true); + } + + private static DefaultAsyncHttpClientConfig.Builder httpClientBuilder() { + return config() + .setKeepAlive(true) + .setMaxConnectionsPerHost(1) + .setMaxConnections(1) + .setConnectTimeout(Duration.ofSeconds(1)) + .setRequestTimeout(Duration.ofSeconds(1)) + .setFollowRedirect(true); + } + + private static void waitForAndAssertResponse(ListenableFuture responseFuture) throws InterruptedException, ExecutionException { + Response response = responseFuture.get(); + if (500 == response.getStatusCode()) { + logger.debug("==============\n" + + "500 response from call\n" + + "Headers:" + response.getHeaders() + '\n' + + "==============\n"); + assertEquals(500, response.getStatusCode(), "Should have 500 status code"); + assertTrue(response.getHeader("X-Exception").contains("invalid.chunk.length"), "Should have failed due to chunking"); + fail("HARD Failing the test due to provided InputStreamBodyGenerator, chunking incorrectly:" + response.getHeader("X-Exception")); + } else { + assertArrayEquals(LARGE_IMAGE_BYTES, response.getResponseBodyAsBytes()); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/EmptyBodyTest.java b/client/src/test/java/org/asynchttpclient/request/body/EmptyBodyTest.java new file mode 100644 index 0000000000..ca3ac69300 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/EmptyBodyTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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.request.body; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.HttpHeaders; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.Response; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Tests case where response doesn't have body. + * + * @author Hubert Iwaniuk + */ +public class EmptyBodyTest extends AbstractBasicTest { + + @Override + public AbstractHandler configureHandler() throws Exception { + return new NoBodyResponseHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testEmptyBody() throws IOException { + try (AsyncHttpClient ahc = asyncHttpClient()) { + final AtomicBoolean err = new AtomicBoolean(false); + final LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); + final AtomicBoolean status = new AtomicBoolean(false); + final AtomicInteger headers = new AtomicInteger(0); + final CountDownLatch latch = new CountDownLatch(1); + + ahc.executeRequest(ahc.prepareGet(getTargetUrl()).build(), new AsyncHandler() { + + @Override + public void onThrowable(Throwable t) { + fail("Got throwable.", t); + err.set(true); + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart e) throws Exception { + byte[] bytes = e.getBodyPartBytes(); + + if (bytes.length != 0) { + String s = new String(bytes); + logger.info("got part: {}", s); + logger.warn("Sampling stacktrace.", new Throwable("trace that, we should not get called for empty body.")); + queue.put(s); + } + return State.CONTINUE; + } + + @Override + public State onStatusReceived(HttpResponseStatus e) { + status.set(true); + return AsyncHandler.State.CONTINUE; + } + + @Override + public State onHeadersReceived(HttpHeaders e) throws Exception { + if (headers.incrementAndGet() == 2) { + throw new Exception("Analyze this."); + } + return State.CONTINUE; + } + + @Override + public Object onCompleted() { + latch.countDown(); + return null; + } + }); + + try { + assertTrue(latch.await(1, TimeUnit.SECONDS), "Latch failed."); + } catch (InterruptedException e) { + fail("Interrupted.", e); + } + assertFalse(err.get()); + assertEquals(0, queue.size()); + assertTrue(status.get()); + assertEquals(1, headers.get()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPutEmptyBody() throws Exception { + try (AsyncHttpClient ahc = asyncHttpClient()) { + Response response = ahc.preparePut(getTargetUrl()).setBody("String").execute().get(); + + assertNotNull(response); + assertEquals(204, response.getStatusCode()); + assertEquals("", response.getResponseBody()); + assertNotNull(response.getResponseBodyAsStream()); + assertEquals(-1, response.getResponseBodyAsStream().read()); + } + } + + private static class NoBodyResponseHandler extends AbstractHandler { + + @Override + public void handle(String s, Request request, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + + if (!"PUT".equalsIgnoreCase(req.getMethod())) { + resp.setStatus(HttpServletResponse.SC_OK); + } else { + resp.setStatus(204); + } + request.setHandled(true); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/FilePartLargeFileTest.java b/client/src/test/java/org/asynchttpclient/request/body/FilePartLargeFileTest.java new file mode 100644 index 0000000000..4cf1d2ee4e --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/FilePartLargeFileTest.java @@ -0,0 +1,88 @@ +/* + * 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 jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.Response; +import org.asynchttpclient.request.body.multipart.FilePart; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.File; +import java.io.IOException; +import java.time.Duration; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.LARGE_IMAGE_FILE; +import static org.asynchttpclient.test.TestUtils.createTempFile; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FilePartLargeFileTest extends AbstractBasicTest { + + @Override + public AbstractHandler configureHandler() throws Exception { + return new AbstractHandler() { + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest req, HttpServletResponse resp) throws IOException { + + ServletInputStream in = req.getInputStream(); + byte[] b = new byte[8192]; + + int count; + int total = 0; + while ((count = in.read(b)) != -1) { + b = new byte[8192]; + total += count; + } + resp.setStatus(200); + resp.addHeader("X-TRANSFERRED", String.valueOf(total)); + resp.getOutputStream().flush(); + resp.getOutputStream().close(); + + baseRequest.setHandled(true); + } + }; + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPutImageFile() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(Duration.ofMinutes(10)))) { + Response response = client.preparePut(getTargetUrl()) + .addBodyPart(new FilePart("test", LARGE_IMAGE_FILE, "application/octet-stream", UTF_8)) + .execute() + .get(); + assertEquals(200, response.getStatusCode()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPutLargeTextFile() throws Exception { + File file = createTempFile(1024 * 1024); + + try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(Duration.ofMinutes(10)))) { + Response response = client.preparePut(getTargetUrl()) + .addBodyPart(new FilePart("test", file, "application/octet-stream", UTF_8)) + .execute() + .get(); + assertEquals(200, response.getStatusCode()); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/InputStreamPartLargeFileTest.java b/client/src/test/java/org/asynchttpclient/request/body/InputStreamPartLargeFileTest.java new file mode 100644 index 0000000000..e0fdfcbbdd --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/InputStreamPartLargeFileTest.java @@ -0,0 +1,116 @@ +/* + * 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.request.body; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.Response; +import org.asynchttpclient.request.body.multipart.InputStreamPart; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.time.Duration; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.LARGE_IMAGE_FILE; +import static org.asynchttpclient.test.TestUtils.createTempFile; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class InputStreamPartLargeFileTest extends AbstractBasicTest { + + @Override + public AbstractHandler configureHandler() throws Exception { + return new AbstractHandler() { + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest req, HttpServletResponse resp) throws IOException { + + ServletInputStream in = req.getInputStream(); + byte[] b = new byte[8192]; + + int count; + int total = 0; + while ((count = in.read(b)) != -1) { + b = new byte[8192]; + total += count; + } + resp.setStatus(200); + resp.addHeader("X-TRANSFERRED", String.valueOf(total)); + resp.getOutputStream().flush(); + resp.getOutputStream().close(); + + baseRequest.setHandled(true); + } + }; + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPutImageFile() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(Duration.ofMinutes(10)))) { + InputStream inputStream = new BufferedInputStream(new FileInputStream(LARGE_IMAGE_FILE)); + Response response = client.preparePut(getTargetUrl()).addBodyPart(new InputStreamPart("test", inputStream, LARGE_IMAGE_FILE.getName(), + LARGE_IMAGE_FILE.length(), "application/octet-stream", UTF_8)).execute().get(); + assertEquals(200, response.getStatusCode()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPutImageFileUnknownSize() throws Exception { + try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(Duration.ofMinutes(10)))) { + InputStream inputStream = new BufferedInputStream(new FileInputStream(LARGE_IMAGE_FILE)); + Response response = client.preparePut(getTargetUrl()).addBodyPart(new InputStreamPart("test", inputStream, LARGE_IMAGE_FILE.getName(), + -1, "application/octet-stream", UTF_8)).execute().get(); + assertEquals(200, response.getStatusCode()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPutLargeTextFile() throws Exception { + File file = createTempFile(1024 * 1024); + InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); + + try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(Duration.ofMinutes(10)))) { + Response response = client.preparePut(getTargetUrl()) + .addBodyPart(new InputStreamPart("test", inputStream, file.getName(), file.length(), + "application/octet-stream", UTF_8)).execute().get(); + assertEquals(200, response.getStatusCode()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPutLargeTextFileUnknownSize() throws Exception { + File file = createTempFile(1024 * 1024); + InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); + + try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(Duration.ofMinutes(10)))) { + Response response = client.preparePut(getTargetUrl()) + .addBodyPart(new InputStreamPart("test", inputStream, file.getName(), -1, + "application/octet-stream", UTF_8)).execute().get(); + assertEquals(200, response.getStatusCode()); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/InputStreamTest.java b/client/src/test/java/org/asynchttpclient/request/body/InputStreamTest.java new file mode 100644 index 0000000000..55cff4323d --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/InputStreamTest.java @@ -0,0 +1,108 @@ +/* + * Copyright 2010 Ning, Inc. + * + * This program is licensed 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: + * + * 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.request.body; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http.HttpHeaders; +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.Response; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class InputStreamTest extends AbstractBasicTest { + + @Override + public AbstractHandler configureHandler() throws Exception { + return new InputStreamHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testInvalidInputStream() throws Exception { + + try (AsyncHttpClient client = asyncHttpClient()) { + HttpHeaders httpHeaders = new DefaultHttpHeaders().add(CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + + InputStream inputStream = new InputStream() { + + int readAllowed; + + @Override + public int available() { + return 1; // Fake + } + + @Override + public int read() { + int fakeCount = readAllowed++; + if (fakeCount == 0) { + return 'a'; + } else if (fakeCount == 1) { + return 'b'; + } else if (fakeCount == 2) { + return 'c'; + } else { + return -1; + } + } + }; + + Response resp = client.preparePost(getTargetUrl()).setHeaders(httpHeaders).setBody(inputStream).execute().get(); + assertNotNull(resp); + // TODO: 18-11-2022 Revisit + assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, resp.getStatusCode()); +// assertEquals(resp.getHeader("X-Param"), "abc"); + } + } + + private static class InputStreamHandler extends AbstractHandler { + @Override + public void handle(String s, Request r, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + if ("POST".equalsIgnoreCase(request.getMethod())) { + byte[] bytes = new byte[3]; + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int read = 0; + while (read > -1) { + read = request.getInputStream().read(bytes); + if (read > 0) { + bos.write(bytes, 0, read); + } + } + + response.setStatus(HttpServletResponse.SC_OK); + response.addHeader("X-Param", bos.toString()); + } else { // this handler is to handle POST request + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + response.getOutputStream().flush(); + response.getOutputStream().close(); + } + } +} 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 0000000000..3260604d3f --- /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/PutFileTest.java b/client/src/test/java/org/asynchttpclient/request/body/PutFileTest.java new file mode 100644 index 0000000000..30100f6586 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/PutFileTest.java @@ -0,0 +1,75 @@ +/* + * 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 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.File; +import java.io.IOException; +import java.io.InputStream; +import java.time.Duration; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.createTempFile; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class PutFileTest extends AbstractBasicTest { + + private void put(int fileSize) throws Exception { + File file = createTempFile(fileSize); + try (AsyncHttpClient client = asyncHttpClient(config().setRequestTimeout(Duration.ofSeconds(2)))) { + Response response = client.preparePut(getTargetUrl()).setBody(file).execute().get(); + assertEquals(response.getStatusCode(), 200); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPutLargeFile() throws Exception { + put(1024 * 1024); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPutSmallFile() throws Exception { + put(1024); + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new AbstractHandler() { + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { + + InputStream is = baseRequest.getInputStream(); + int read; + do { + // drain upload + read = is.read(); + } while (read >= 0); + + response.setStatus(200); + response.getOutputStream().flush(); + response.getOutputStream().close(); + baseRequest.setHandled(true); + } + }; + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/TransferListenerTest.java b/client/src/test/java/org/asynchttpclient/request/body/TransferListenerTest.java new file mode 100644 index 0000000000..e4cffadd0f --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/TransferListenerTest.java @@ -0,0 +1,257 @@ +/* + * 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.handler.codec.http.HttpHeaders; +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.Response; +import org.asynchttpclient.handler.TransferCompletionHandler; +import org.asynchttpclient.handler.TransferListener; +import org.asynchttpclient.request.body.generator.FileBodyGenerator; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.File; +import java.io.IOException; +import java.time.Duration; +import java.util.Enumeration; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.createTempFile; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class TransferListenerTest extends AbstractBasicTest { + + @Override + public AbstractHandler configureHandler() throws Exception { + return new BasicHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicGetTest() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + final AtomicReference throwable = new AtomicReference<>(); + final AtomicReference hSent = new AtomicReference<>(); + final AtomicReference hRead = new AtomicReference<>(); + final AtomicReference bb = new AtomicReference<>(); + final AtomicBoolean completed = new AtomicBoolean(false); + + TransferCompletionHandler tl = new TransferCompletionHandler(); + tl.addTransferListener(new TransferListener() { + + @Override + public void onRequestHeadersSent(HttpHeaders headers) { + hSent.set(headers); + } + + @Override + public void onResponseHeadersReceived(HttpHeaders headers) { + hRead.set(headers); + } + + @Override + public void onBytesReceived(byte[] b) { + if (b.length != 0) { + bb.set(b); + } + } + + @Override + public void onBytesSent(long amount, long current, long total) { + } + + @Override + public void onRequestResponseCompleted() { + completed.set(true); + } + + @Override + public void onThrowable(Throwable t) { + throwable.set(t); + } + }); + + Response response = c.prepareGet(getTargetUrl()).execute(tl).get(); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertNotNull(hRead.get()); + assertNotNull(hSent.get()); + assertNull(bb.get()); + assertNull(throwable.get()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicPutFileTest() throws Exception { + final AtomicReference throwable = new AtomicReference<>(); + final AtomicReference hSent = new AtomicReference<>(); + final AtomicReference hRead = new AtomicReference<>(); + final AtomicInteger bbReceivedLength = new AtomicInteger(0); + final AtomicLong bbSentLength = new AtomicLong(0L); + + final AtomicBoolean completed = new AtomicBoolean(false); + + File file = createTempFile(1024 * 100 * 10); + + long timeout = file.length() / 1000; + + try (AsyncHttpClient client = asyncHttpClient(config().setConnectTimeout(Duration.ofMillis(timeout)))) { + TransferCompletionHandler tl = new TransferCompletionHandler(); + tl.addTransferListener(new TransferListener() { + + @Override + public void onRequestHeadersSent(HttpHeaders headers) { + hSent.set(headers); + } + + @Override + public void onResponseHeadersReceived(HttpHeaders headers) { + hRead.set(headers); + } + + @Override + public void onBytesReceived(byte[] b) { + bbReceivedLength.addAndGet(b.length); + } + + @Override + public void onBytesSent(long amount, long current, long total) { + bbSentLength.addAndGet(amount); + } + + @Override + public void onRequestResponseCompleted() { + completed.set(true); + } + + @Override + public void onThrowable(Throwable t) { + throwable.set(t); + } + }); + + Response response = client.preparePut(getTargetUrl()).setBody(file).execute(tl).get(); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertNotNull(hRead.get()); + assertNotNull(hSent.get()); + assertEquals(file.length(), bbReceivedLength.get(), "Number of received bytes incorrect"); + assertEquals(file.length(), bbSentLength.get(), "Number of sent bytes incorrect"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void basicPutFileBodyGeneratorTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + final AtomicReference throwable = new AtomicReference<>(); + final AtomicReference hSent = new AtomicReference<>(); + final AtomicReference hRead = new AtomicReference<>(); + final AtomicInteger bbReceivedLength = new AtomicInteger(0); + final AtomicLong bbSentLength = new AtomicLong(0L); + + final AtomicBoolean completed = new AtomicBoolean(false); + + File file = createTempFile(1024 * 100 * 10); + + TransferCompletionHandler tl = new TransferCompletionHandler(); + tl.addTransferListener(new TransferListener() { + + @Override + public void onRequestHeadersSent(HttpHeaders headers) { + hSent.set(headers); + } + + @Override + public void onResponseHeadersReceived(HttpHeaders headers) { + hRead.set(headers); + } + + @Override + public void onBytesReceived(byte[] b) { + bbReceivedLength.addAndGet(b.length); + } + + @Override + public void onBytesSent(long amount, long current, long total) { + bbSentLength.addAndGet(amount); + } + + @Override + public void onRequestResponseCompleted() { + completed.set(true); + } + + @Override + public void onThrowable(Throwable t) { + throwable.set(t); + } + }); + + Response response = client.preparePut(getTargetUrl()).setBody(new FileBodyGenerator(file)).execute(tl).get(); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertNotNull(hRead.get()); + assertNotNull(hSent.get()); + assertEquals(file.length(), bbReceivedLength.get(), "Number of received bytes incorrect"); + assertEquals(file.length(), bbSentLength.get(), "Number of sent bytes incorrect"); + } + } + + private static class BasicHandler extends AbstractHandler { + + @Override + public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + + Enumeration e = httpRequest.getHeaderNames(); + String param; + while (e.hasMoreElements()) { + param = e.nextElement().toString(); + httpResponse.addHeader("X-" + param, httpRequest.getHeader(param)); + } + + int size = 10 * 1024; + if (httpRequest.getContentLength() > 0) { + size = httpRequest.getContentLength(); + } + byte[] bytes = new byte[size]; + if (bytes.length > 0) { + int read = 0; + while (read != -1) { + read = httpRequest.getInputStream().read(bytes); + if (read > 0) { + httpResponse.getOutputStream().write(bytes, 0, read); + } + } + } + + httpResponse.setStatus(200); + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/ZeroCopyFileTest.java b/client/src/test/java/org/asynchttpclient/request/body/ZeroCopyFileTest.java new file mode 100644 index 0000000000..374c1e121d --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/ZeroCopyFileTest.java @@ -0,0 +1,204 @@ +/* + * 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.handler.codec.http.HttpHeaders; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncCompletionHandler; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.BasicHttpsTest; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.Response; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE; +import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE_STRING; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Zero copy test which use FileChannel.transfer under the hood . The same SSL test is also covered in {@link BasicHttpsTest} + */ +public class ZeroCopyFileTest extends AbstractBasicTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void zeroCopyPostTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + final AtomicBoolean headerSent = new AtomicBoolean(false); + final AtomicBoolean operationCompleted = new AtomicBoolean(false); + + Response resp = client.preparePost("http://localhost/" + port1 + '/').setBody(SIMPLE_TEXT_FILE).execute(new AsyncCompletionHandler() { + + @Override + public State onHeadersWritten() { + headerSent.set(true); + return State.CONTINUE; + } + + @Override + public State onContentWritten() { + operationCompleted.set(true); + return State.CONTINUE; + } + + @Override + public Response onCompleted(Response response) { + return response; + } + }).get(); + + assertNotNull(resp); + assertEquals(HttpServletResponse.SC_OK, resp.getStatusCode()); + assertEquals(SIMPLE_TEXT_FILE_STRING, resp.getResponseBody()); + assertTrue(operationCompleted.get()); + assertTrue(headerSent.get()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void zeroCopyPutTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + Future f = client.preparePut("http://localhost/" + port1 + '/').setBody(SIMPLE_TEXT_FILE).execute(); + Response resp = f.get(); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); + assertEquals(resp.getResponseBody(), SIMPLE_TEXT_FILE_STRING); + } + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new ZeroCopyHandler(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void zeroCopyFileTest() throws Exception { + File tmp = new File(System.getProperty("java.io.tmpdir") + File.separator + "zeroCopy.txt"); + tmp.deleteOnExit(); + try (AsyncHttpClient client = asyncHttpClient()) { + try (OutputStream stream = Files.newOutputStream(tmp.toPath())) { + Response resp = client.preparePost("http://localhost/" + port1 + '/').setBody(SIMPLE_TEXT_FILE).execute(new AsyncHandler() { + + @Override + public void onThrowable(Throwable t) { + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { + stream.write(bodyPart.getBodyPartBytes()); + return State.CONTINUE; + } + + @Override + public State onStatusReceived(HttpResponseStatus responseStatus) { + return State.CONTINUE; + } + + @Override + public State onHeadersReceived(HttpHeaders headers) { + return State.CONTINUE; + } + + @Override + public Response onCompleted() { + return null; + } + }).get(); + + assertNull(resp); + assertEquals(SIMPLE_TEXT_FILE.length(), tmp.length()); + } + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void zeroCopyFileWithBodyManipulationTest() throws Exception { + File tmp = new File(System.getProperty("java.io.tmpdir") + File.separator + "zeroCopy.txt"); + tmp.deleteOnExit(); + try (AsyncHttpClient client = asyncHttpClient()) { + try (OutputStream stream = Files.newOutputStream(tmp.toPath())) { + Response resp = client.preparePost("http://localhost/" + port1 + '/').setBody(SIMPLE_TEXT_FILE).execute(new AsyncHandler() { + + @Override + public void onThrowable(Throwable t) { + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { + stream.write(bodyPart.getBodyPartBytes()); + + if (bodyPart.getBodyPartBytes().length == 0) { + return State.ABORT; + } + + return State.CONTINUE; + } + + @Override + public State onStatusReceived(HttpResponseStatus responseStatus) { + return State.CONTINUE; + } + + @Override + public State onHeadersReceived(HttpHeaders headers) { + return State.CONTINUE; + } + + @Override + public Response onCompleted() { + return null; + } + }).get(); + assertNull(resp); + assertEquals(SIMPLE_TEXT_FILE.length(), tmp.length()); + } + } + } + + private static class ZeroCopyHandler extends AbstractHandler { + + @Override + public void handle(String s, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + + int size = 10 * 1024; + if (httpRequest.getContentLength() > 0) { + size = httpRequest.getContentLength(); + } + byte[] bytes = new byte[size]; + if (bytes.length > 0) { + httpRequest.getInputStream().read(bytes); + httpResponse.getOutputStream().write(bytes); + } + + httpResponse.setStatus(200); + httpResponse.getOutputStream().flush(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/generator/ByteArrayBodyGeneratorTest.java b/client/src/test/java/org/asynchttpclient/request/body/generator/ByteArrayBodyGeneratorTest.java new file mode 100644 index 0000000000..81da4d7341 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/generator/ByteArrayBodyGeneratorTest.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.generator; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.asynchttpclient.request.body.Body; +import org.asynchttpclient.request.body.Body.BodyState; + +import java.io.IOException; +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Bryan Davis bpd@keynetics.com + */ +public class ByteArrayBodyGeneratorTest { + + private final Random random = new Random(); + private static final int CHUNK_SIZE = 1024 * 8; + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSingleRead() throws IOException { + final int srcArraySize = CHUNK_SIZE - 1; + final byte[] srcArray = new byte[srcArraySize]; + random.nextBytes(srcArray); + + final ByteArrayBodyGenerator babGen = new ByteArrayBodyGenerator(srcArray); + final Body body = babGen.createBody(); + + final ByteBuf chunkBuffer = Unpooled.buffer(CHUNK_SIZE); + + try { + // should take 1 read to get through the srcArray + body.transferTo(chunkBuffer); + assertEquals(srcArraySize, chunkBuffer.readableBytes(), "bytes read"); + chunkBuffer.clear(); + + assertEquals(BodyState.STOP, body.transferTo(chunkBuffer), "body at EOF"); + } finally { + chunkBuffer.release(); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testMultipleReads() throws IOException { + final int srcArraySize = 3 * CHUNK_SIZE + 42; + final byte[] srcArray = new byte[srcArraySize]; + random.nextBytes(srcArray); + + final ByteArrayBodyGenerator babGen = new ByteArrayBodyGenerator(srcArray); + final Body body = babGen.createBody(); + + final ByteBuf chunkBuffer = Unpooled.buffer(CHUNK_SIZE); + + try { + int reads = 0; + int bytesRead = 0; + while (body.transferTo(chunkBuffer) != BodyState.STOP) { + reads += 1; + bytesRead += chunkBuffer.readableBytes(); + chunkBuffer.clear(); + } + assertEquals(4, reads, "reads to drain generator"); + assertEquals(srcArraySize, bytesRead, "bytes read"); + } finally { + chunkBuffer.release(); + } + } +} 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 new file mode 100755 index 0000000000..7c2a3579bf --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/generator/FeedableBodyGeneratorTest.java @@ -0,0 +1,116 @@ +/* + * 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. + * 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.request.body.generator; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.asynchttpclient.request.body.Body; +import org.asynchttpclient.request.body.Body.BodyState; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FeedableBodyGeneratorTest { + + private UnboundedQueueFeedableBodyGenerator feedableBodyGenerator; + private TestFeedListener listener; + + @BeforeEach + public void setUp() { + feedableBodyGenerator = new UnboundedQueueFeedableBodyGenerator(); + listener = new TestFeedListener(); + feedableBodyGenerator.setListener(listener); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void feedNotifiesListener() throws Exception { + feedableBodyGenerator.feed(Unpooled.EMPTY_BUFFER, false); + feedableBodyGenerator.feed(Unpooled.EMPTY_BUFFER, true); + assertEquals(2, listener.getCalls()); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void readingBytesReturnsFedContentWithoutChunkBoundaries() throws Exception { + byte[] content = "Test123".getBytes(StandardCharsets.US_ASCII); + + ByteBuf source = Unpooled.wrappedBuffer(content); + ByteBuf target = Unpooled.buffer(1); + + try { + feedableBodyGenerator.feed(source, true); + Body body = feedableBodyGenerator.createBody(); + assertArrayEquals("Test123".getBytes(StandardCharsets.US_ASCII), readFromBody(body)); + assertEquals(body.transferTo(target), BodyState.STOP); + } finally { + source.release(); + target.release(); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void returnZeroToSuspendStreamWhenNothingIsInQueue() throws Exception { + byte[] content = "Test123".getBytes(StandardCharsets.US_ASCII); + + ByteBuf source = Unpooled.wrappedBuffer(content); + ByteBuf target = Unpooled.buffer(1); + + try { + feedableBodyGenerator.feed(source, false); + + Body body = feedableBodyGenerator.createBody(); + assertArrayEquals("Test123".getBytes(StandardCharsets.US_ASCII), readFromBody(body)); + assertEquals(body.transferTo(target), BodyState.SUSPEND); + } finally { + source.release(); + target.release(); + } + } + + private static byte[] readFromBody(Body body) throws IOException { + ByteBuf byteBuf = Unpooled.buffer(512); + try { + body.transferTo(byteBuf); + byte[] readBytes = new byte[byteBuf.readableBytes()]; + byteBuf.readBytes(readBytes); + return readBytes; + } finally { + byteBuf.release(); + } + } + + private static class TestFeedListener implements FeedListener { + + private int calls; + + @Override + public void onContentAdded() { + calls++; + } + + @Override + public void onError(Throwable t) { + } + + int getCalls() { + return calls; + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/multipart/MultipartBasicAuthTest.java b/client/src/test/java/org/asynchttpclient/request/body/multipart/MultipartBasicAuthTest.java new file mode 100644 index 0000000000..1941ea5494 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/multipart/MultipartBasicAuthTest.java @@ -0,0 +1,108 @@ +/* + * 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.request.body.multipart; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.BasicAuthTest; +import org.asynchttpclient.BoundRequestBuilder; +import org.asynchttpclient.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.junit.jupiter.api.BeforeEach; + +import java.io.File; +import java.util.function.Function; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static io.netty.handler.codec.http.HttpHeaderNames.EXPECT; +import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_OCTET_STREAM; +import static io.netty.handler.codec.http.HttpHeaderValues.CONTINUE; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.basicAuthRealm; +import static org.asynchttpclient.test.TestUtils.ADMIN; +import static org.asynchttpclient.test.TestUtils.USER; +import static org.asynchttpclient.test.TestUtils.addBasicAuthHandler; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.asynchttpclient.test.TestUtils.createTempFile; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MultipartBasicAuthTest extends AbstractBasicTest { + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector1 = addHttpConnector(server); + addBasicAuthHandler(server, configureHandler()); + server.start(); + port1 = connector1.getLocalPort(); + logger.info("Local HTTP server started successfully"); + } + + @Override + public AbstractHandler configureHandler() throws Exception { + return new BasicAuthTest.SimpleHandler(); + } + + private void expectHttpResponse(Function f, int expectedResponseCode) throws Throwable { + File file = createTempFile(1024 * 1024); + + try (AsyncHttpClient client = asyncHttpClient()) { + Response response = f.apply(client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", file, APPLICATION_OCTET_STREAM.toString(), UTF_8))) + .execute() + .get(); + assertEquals(expectedResponseCode, response.getStatusCode()); + } + } + + @RepeatedIfExceptionsTest(repeats = 3) + public void noRealmCausesServerToCloseSocket() throws Throwable { + expectHttpResponse(rb -> rb, 401); + } + + @RepeatedIfExceptionsTest(repeats = 3) + public void unauthorizedNonPreemptiveRealmCausesServerToCloseSocket() throws Throwable { + expectHttpResponse(rb -> rb.setRealm(basicAuthRealm(USER, "NOT-ADMIN")), 401); + } + + private void expectSuccess(Function f) throws Exception { + File file = createTempFile(1024 * 1024); + + try (AsyncHttpClient client = asyncHttpClient()) { + for (int i = 0; i < 20; i++) { + Response response = f.apply(client.preparePut(getTargetUrl()) + .addBodyPart(new FilePart("test", file, APPLICATION_OCTET_STREAM.toString(), UTF_8))) + .execute().get(); + assertEquals(response.getStatusCode(), 200); + assertEquals(response.getResponseBodyAsBytes().length, Integer.valueOf(response.getHeader("X-" + CONTENT_LENGTH)).intValue()); + } + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void authorizedPreemptiveRealmWorks() throws Exception { + expectSuccess(rb -> rb.setRealm(basicAuthRealm(USER, ADMIN).setUsePreemptiveAuth(true))); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void authorizedNonPreemptiveRealmWorksWithExpectContinue() throws Exception { + expectSuccess(rb -> rb.setRealm(basicAuthRealm(USER, ADMIN)).setHeader(EXPECT, CONTINUE)); + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/multipart/MultipartBodyTest.java b/client/src/test/java/org/asynchttpclient/request/body/multipart/MultipartBodyTest.java new file mode 100644 index 0000000000..f31046ea2f --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/multipart/MultipartBodyTest.java @@ -0,0 +1,147 @@ +/* + * 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.request.body.multipart; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http.EmptyHttpHeaders; +import org.asynchttpclient.request.body.Body.BodyState; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class MultipartBodyTest { + + private static final List PARTS = new ArrayList<>(); + private static final long MAX_MULTIPART_CONTENT_LENGTH_ESTIMATE; + + static { + try { + PARTS.add(new FilePart("filePart", getTestfile())); + } catch (URISyntaxException e) { + throw new ExceptionInInitializerError(e); + } + PARTS.add(new ByteArrayPart("baPart", "testMultiPart".getBytes(UTF_8), "application/test", UTF_8, "fileName")); + PARTS.add(new StringPart("stringPart", "testString")); + } + + static { + try (MultipartBody dummyBody = buildMultipart()) { + // separator is random + MAX_MULTIPART_CONTENT_LENGTH_ESTIMATE = dummyBody.getContentLength() + 100; + } + } + + private static File getTestfile() throws URISyntaxException { + final ClassLoader cl = MultipartBodyTest.class.getClassLoader(); + final URL url = cl.getResource("textfile.txt"); + assertNotNull(url); + return new File(url.toURI()); + } + + private static MultipartBody buildMultipart() { + List parts = new ArrayList<>(PARTS); + try { + File testFile = getTestfile(); + InputStream inputStream = new BufferedInputStream(new FileInputStream(testFile)); + parts.add(new InputStreamPart("isPart", inputStream, testFile.getName(), testFile.length())); + } catch (URISyntaxException | FileNotFoundException e) { + throw new ExceptionInInitializerError(e); + } + return MultipartUtils.newMultipartBody(parts, EmptyHttpHeaders.INSTANCE); + } + + private static long transferWithCopy(MultipartBody multipartBody, int bufferSize) throws IOException { + long transferred = 0; + final ByteBuf buffer = Unpooled.buffer(bufferSize); + try { + while (multipartBody.transferTo(buffer) != BodyState.STOP) { + transferred += buffer.readableBytes(); + buffer.clear(); + } + return transferred; + } finally { + buffer.release(); + } + } + + private static long transferZeroCopy(MultipartBody multipartBody, int bufferSize) throws IOException { + + final ByteBuffer buffer = ByteBuffer.allocate(bufferSize); + final AtomicLong transferred = new AtomicLong(); + + WritableByteChannel mockChannel = new WritableByteChannel() { + @Override + public boolean isOpen() { + return true; + } + + @Override + public void close() { + } + + @Override + public int write(ByteBuffer src) { + int written = src.remaining(); + transferred.set(transferred.get() + written); + src.position(src.limit()); + return written; + } + }; + + while (transferred.get() < multipartBody.getContentLength()) { + multipartBody.transferTo(mockChannel); + buffer.clear(); + } + return transferred.get(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void transferWithCopy() throws Exception { + for (int bufferLength = 1; bufferLength < MAX_MULTIPART_CONTENT_LENGTH_ESTIMATE + 1; bufferLength++) { + try (MultipartBody multipartBody = buildMultipart()) { + long transferred = transferWithCopy(multipartBody, bufferLength); + assertEquals(multipartBody.getContentLength(), transferred); + } + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void transferZeroCopy() throws Exception { + for (int bufferLength = 1; bufferLength < MAX_MULTIPART_CONTENT_LENGTH_ESTIMATE + 1; bufferLength++) { + try (MultipartBody multipartBody = buildMultipart()) { + long transferred = transferZeroCopy(multipartBody, bufferLength); + assertEquals(multipartBody.getContentLength(), transferred); + } + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/multipart/MultipartUploadTest.java b/client/src/test/java/org/asynchttpclient/request/body/multipart/MultipartUploadTest.java new file mode 100644 index 0000000000..4a79e52dce --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/multipart/MultipartUploadTest.java @@ -0,0 +1,429 @@ +/* + * 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.multipart; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.fileupload2.FileItemIterator; +import org.apache.commons.fileupload2.FileItemStream; +import org.apache.commons.fileupload2.FileUploadException; +import org.apache.commons.fileupload2.jaksrvlt.JakSrvltFileUpload; +import org.apache.commons.fileupload2.util.Streams; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.asynchttpclient.AbstractBasicTest; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.Request; +import org.asynchttpclient.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.jupiter.api.BeforeEach; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Writer; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.zip.GZIPInputStream; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.post; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.asynchttpclient.test.TestUtils.getClasspathFile; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author dominict + */ +public class MultipartUploadTest extends AbstractBasicTest { + + @BeforeEach + public void setUp() throws Exception { + server = new Server(); + ServerConnector connector = addHttpConnector(server); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.addServlet(new ServletHolder(new MockMultipartUploadServlet()), "/upload"); + server.setHandler(context); + server.start(); + port1 = connector.getLocalPort(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSendingSmallFilesAndByteArray() throws Exception { + String expectedContents = "filecontent: hello"; + String expectedContents2 = "gzipcontent: hello"; + String expectedContents3 = "filecontent: hello2"; + String testResource1 = "textfile.txt"; + String testResource2 = "gzip.txt.gz"; + String testResource3 = "textfile2.txt"; + + File testResource1File = getClasspathFile(testResource1); + File testResource2File = getClasspathFile(testResource2); + File testResource3File = getClasspathFile(testResource3); + InputStream inputStreamFile1 = new BufferedInputStream(new FileInputStream(testResource1File)); + InputStream inputStreamFile2 = new BufferedInputStream(new FileInputStream(testResource2File)); + InputStream inputStreamFile3 = new BufferedInputStream(new FileInputStream(testResource3File)); + + List testFiles = new ArrayList<>(); + testFiles.add(testResource1File); + testFiles.add(testResource2File); + testFiles.add(testResource3File); + testFiles.add(testResource3File); + testFiles.add(testResource2File); + testFiles.add(testResource1File); + + List expected = new ArrayList<>(); + expected.add(expectedContents); + expected.add(expectedContents2); + expected.add(expectedContents3); + expected.add(expectedContents3); + expected.add(expectedContents2); + expected.add(expectedContents); + + List gzipped = new ArrayList<>(); + gzipped.add(false); + gzipped.add(true); + gzipped.add(false); + gzipped.add(false); + gzipped.add(true); + gzipped.add(false); + + File tmpFile = File.createTempFile("textbytearray", ".txt"); + try (OutputStream os = Files.newOutputStream(tmpFile.toPath())) { + IOUtils.write(expectedContents.getBytes(UTF_8), os); + + testFiles.add(tmpFile); + expected.add(expectedContents); + gzipped.add(false); + } + + try (AsyncHttpClient c = asyncHttpClient(config())) { + Request r = post("http://localhost/" + ':' + port1 + "/upload") + .addBodyPart(new FilePart("file1", testResource1File, "text/plain", UTF_8)) + .addBodyPart(new FilePart("file2", testResource2File, "application/x-gzip", null)) + .addBodyPart(new StringPart("Name", "Dominic")) + .addBodyPart(new FilePart("file3", testResource3File, "text/plain", UTF_8)) + .addBodyPart(new StringPart("Age", "3")).addBodyPart(new StringPart("Height", "shrimplike")) + .addBodyPart(new InputStreamPart("inputStream3", inputStreamFile3, testResource3File.getName(), testResource3File.length(), "text/plain", UTF_8)) + .addBodyPart(new InputStreamPart("inputStream2", inputStreamFile2, testResource2File.getName(), testResource2File.length(), "application/x-gzip", null)) + .addBodyPart(new StringPart("Hair", "ridiculous")).addBodyPart(new ByteArrayPart("file4", + expectedContents.getBytes(UTF_8), "text/plain", UTF_8, "bytearray.txt")) + .addBodyPart(new InputStreamPart("inputStream1", inputStreamFile1, testResource1File.getName(), testResource1File.length(), "text/plain", UTF_8)) + .build(); + + Response res = c.executeRequest(r).get(); + + assertEquals(200, res.getStatusCode()); + + testSentFile(expected, testFiles, res, gzipped); + } + } + + private void sendEmptyFile0(boolean disableZeroCopy) throws Exception { + File file = getClasspathFile("empty.txt"); + try (AsyncHttpClient client = asyncHttpClient(config().setDisableZeroCopy(disableZeroCopy))) { + Request r = post("http://localhost/" + ':' + port1 + "/upload") + .addBodyPart(new FilePart("file", file, "text/plain", UTF_8)).build(); + + Response res = client.executeRequest(r).get(); + assertEquals(res.getStatusCode(), 200); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void sendEmptyFile() throws Exception { + sendEmptyFile0(true); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void sendEmptyFileZeroCopy() throws Exception { + sendEmptyFile0(false); + } + + private void sendEmptyFileInputStream(boolean disableZeroCopy) throws Exception { + File file = getClasspathFile("empty.txt"); + try (AsyncHttpClient client = asyncHttpClient(config().setDisableZeroCopy(disableZeroCopy)); + InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { + Request r = post("http://localhost/" + ':' + port1 + "/upload") + .addBodyPart(new InputStreamPart("file", inputStream, file.getName(), file.length(), "text/plain", UTF_8)).build(); + + Response res = client.executeRequest(r).get(); + assertEquals(200, res.getStatusCode()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSendEmptyFileInputStream() throws Exception { + sendEmptyFileInputStream(true); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSendEmptyFileInputStreamZeroCopy() throws Exception { + sendEmptyFileInputStream(false); + } + + private void sendFileInputStream(boolean useContentLength, boolean disableZeroCopy) throws Exception { + File file = getClasspathFile("textfile.txt"); + try (AsyncHttpClient c = asyncHttpClient(config().setDisableZeroCopy(disableZeroCopy)); + InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { + + InputStreamPart part; + if (useContentLength) { + part = new InputStreamPart("file", inputStream, file.getName(), file.length()); + } else { + part = new InputStreamPart("file", inputStream, file.getName()); + } + Request r = post("http://localhost/" + ':' + port1 + "/upload").addBodyPart(part).build(); + + Response res = c.executeRequest(r).get(); + assertEquals(200, res.getStatusCode()); + } catch (ExecutionException ex) { + ex.getCause().printStackTrace(); + throw ex; + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSendFileInputStreamUnknownContentLength() throws Exception { + sendFileInputStream(false, true); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSendFileInputStreamZeroCopyUnknownContentLength() throws Exception { + sendFileInputStream(false, false); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSendFileInputStreamKnownContentLength() throws Exception { + sendFileInputStream(true, true); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSendFileInputStreamZeroCopyKnownContentLength() throws Exception { + sendFileInputStream(true, false); + } + + /** + * Test that the files were sent, based on the response from the servlet + */ + private static void testSentFile(List expectedContents, List sourceFiles, Response r, + List deflate) throws IOException { + String content = r.getResponseBody(); + assertNotNull(content); + logger.debug(content); + + String[] contentArray = content.split("\\|\\|"); + // TODO: this fail on win32 + assertEquals(contentArray.length, 2); + + String tmpFiles = contentArray[1]; + assertNotNull(tmpFiles); + assertTrue(tmpFiles.trim().length() > 2); + tmpFiles = tmpFiles.substring(1, tmpFiles.length() - 1); + + String[] responseFiles = tmpFiles.split(","); + assertNotNull(responseFiles); + assertEquals(responseFiles.length, sourceFiles.size()); + + logger.debug(Arrays.toString(responseFiles)); + + int i = 0; + for (File sourceFile : sourceFiles) { + + File tmp = null; + try { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] sourceBytes; + try (InputStream instream = Files.newInputStream(sourceFile.toPath())) { + byte[] buf = new byte[8092]; + int len; + while ((len = instream.read(buf)) > 0) { + baos.write(buf, 0, len); + } + logger.debug("================"); + logger.debug("Length of file: " + baos.toByteArray().length); + logger.debug("Contents: " + Arrays.toString(baos.toByteArray())); + logger.debug("================"); + System.out.flush(); + sourceBytes = baos.toByteArray(); + } + + tmp = new File(responseFiles[i].trim()); + logger.debug("=============================="); + logger.debug(tmp.getAbsolutePath()); + logger.debug("=============================="); + System.out.flush(); + assertTrue(tmp.exists()); + + byte[] bytes; + try (InputStream instream = Files.newInputStream(tmp.toPath())) { + ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); + byte[] buf = new byte[8092]; + int len; + while ((len = instream.read(buf)) > 0) { + baos2.write(buf, 0, len); + } + bytes = baos2.toByteArray(); + assertArrayEquals(bytes, sourceBytes); + } + + if (!deflate.get(i)) { + String helloString = new String(bytes); + assertEquals(helloString, expectedContents.get(i)); + } else { + try (InputStream instream = Files.newInputStream(tmp.toPath())) { + ByteArrayOutputStream baos3 = new ByteArrayOutputStream(); + GZIPInputStream deflater = new GZIPInputStream(instream); + try { + byte[] buf3 = new byte[8092]; + int len3; + while ((len3 = deflater.read(buf3)) > 0) { + baos3.write(buf3, 0, len3); + } + } finally { + deflater.close(); + } + + String helloString = baos3.toString(); + + assertEquals(expectedContents.get(i), helloString); + } + } + } catch (Exception e) { + throw e; + } finally { + if (tmp != null) { + FileUtils.deleteQuietly(tmp); + } + i++; + } + } + } + + + public static class MockMultipartUploadServlet extends HttpServlet { + + private static final Logger LOGGER = LoggerFactory.getLogger(MockMultipartUploadServlet.class); + + private static final long serialVersionUID = 1L; + private int filesProcessed; + private int stringsProcessed; + + MockMultipartUploadServlet() { + stringsProcessed = 0; + } + + synchronized void resetFilesProcessed() { + filesProcessed = 0; + } + + private synchronized int incrementFilesProcessed() { + return ++filesProcessed; + } + + int getFilesProcessed() { + return filesProcessed; + } + + synchronized void resetStringsProcessed() { + stringsProcessed = 0; + } + + private synchronized int incrementStringsProcessed() { + return ++stringsProcessed; + + } + + public int getStringsProcessed() { + return stringsProcessed; + } + + @Override + public void service(HttpServletRequest request, HttpServletResponse response) throws IOException { + // Check that we have a file upload request + boolean isMultipart = JakSrvltFileUpload.isMultipartContent(request); + if (isMultipart) { + List files = new ArrayList<>(); + JakSrvltFileUpload upload = new JakSrvltFileUpload(); + // Parse the request + FileItemIterator iter; + try { + iter = upload.getItemIterator(request); + while (iter.hasNext()) { + FileItemStream item = iter.next(); + String name = item.getFieldName(); + try (InputStream stream = item.openStream()) { + + if (item.isFormField()) { + LOGGER.debug("Form field " + name + " with value " + Streams.asString(stream) + " detected."); + incrementStringsProcessed(); + } else { + LOGGER.debug("File field " + name + " with file name " + item.getName() + " detected."); + // Process the input stream + File tmpFile = File.createTempFile(UUID.randomUUID() + "_MockUploadServlet", + ".tmp"); + tmpFile.deleteOnExit(); + try (OutputStream os = Files.newOutputStream(tmpFile.toPath())) { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = stream.read(buffer)) != -1) { + os.write(buffer, 0, bytesRead); + } + incrementFilesProcessed(); + files.add(tmpFile.getAbsolutePath()); + } + } + } + } + } catch (FileUploadException e) { + // + } + try (Writer w = response.getWriter()) { + w.write(Integer.toString(getFilesProcessed())); + resetFilesProcessed(); + resetStringsProcessed(); + w.write("||"); + w.write(files.toString()); + } + } else { + try (Writer w = response.getWriter()) { + w.write(Integer.toString(getFilesProcessed())); + resetFilesProcessed(); + resetStringsProcessed(); + w.write("||"); + } + } + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/request/body/multipart/part/MultipartPartTest.java b/client/src/test/java/org/asynchttpclient/request/body/multipart/part/MultipartPartTest.java new file mode 100644 index 0000000000..a5711015de --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/request/body/multipart/part/MultipartPartTest.java @@ -0,0 +1,279 @@ +/* + * 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.request.body.multipart.part; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaders; +import org.apache.commons.io.FileUtils; +import org.asynchttpclient.request.body.multipart.FileLikePart; +import org.asynchttpclient.request.body.multipart.MultipartBody; +import org.asynchttpclient.request.body.multipart.MultipartUtils; +import org.asynchttpclient.request.body.multipart.Part; +import org.asynchttpclient.request.body.multipart.StringPart; +import org.asynchttpclient.request.body.multipart.part.PartVisitor.CounterPartVisitor; +import org.asynchttpclient.test.TestUtils; + +import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MultipartPartTest { + + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitStart() { + TestFileLikePart fileLikePart = new TestFileLikePart("Name"); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, new byte[10])) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitStart(counterVisitor); + assertEquals(12, counterVisitor.getCount(), "CounterPartVisitor count for visitStart should match EXTRA_BYTES count plus boundary bytes count"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitStartZeroSizedByteArray() { + TestFileLikePart fileLikePart = new TestFileLikePart("Name"); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, EMPTY_BYTE_ARRAY)) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitStart(counterVisitor); + assertEquals(2, counterVisitor.getCount(), "CounterPartVisitor count for visitStart should match EXTRA_BYTES count when boundary byte array is of size zero"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitDispositionHeaderWithoutFileName() { + TestFileLikePart fileLikePart = new TestFileLikePart("Name"); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, EMPTY_BYTE_ARRAY)) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitDispositionHeader(counterVisitor); + assertEquals(45, counterVisitor.getCount(), "CounterPartVisitor count for visitDispositionHeader should be equal to " + + "CRLF_BYTES length + CONTENT_DISPOSITION_BYTES length + part name length when file name is not specified"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitDispositionHeaderWithFileName() { + TestFileLikePart fileLikePart = new TestFileLikePart("baPart", null, null, null, null, "fileName"); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, EMPTY_BYTE_ARRAY)) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitDispositionHeader(counterVisitor); + assertEquals(68, counterVisitor.getCount(), "CounterPartVisitor count for visitDispositionHeader should be equal to " + + "CRLF_BYTES length + CONTENT_DISPOSITION_BYTES length + part name length + file name length when" + " both part name and file name are present"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitDispositionHeaderWithoutName() { + // with fileName + TestFileLikePart fileLikePart = new TestFileLikePart(null, null, null, null, null, "fileName"); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, EMPTY_BYTE_ARRAY)) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitDispositionHeader(counterVisitor); + assertEquals(53, counterVisitor.getCount(), "CounterPartVisitor count for visitDispositionHeader should be equal to " + + "CRLF_BYTES length + CONTENT_DISPOSITION_BYTES length + file name length when part name is not specified"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitContentTypeHeaderWithCharset() { + TestFileLikePart fileLikePart = new TestFileLikePart(null, "application/test", UTF_8, null, null); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, EMPTY_BYTE_ARRAY)) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitContentTypeHeader(counterVisitor); + assertEquals(47, counterVisitor.getCount(), "CounterPartVisitor count for visitContentTypeHeader should be equal to " + + "CRLF_BYTES length + CONTENT_TYPE_BYTES length + contentType length + charset length"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitContentTypeHeaderWithoutCharset() { + TestFileLikePart fileLikePart = new TestFileLikePart(null, "application/test"); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, EMPTY_BYTE_ARRAY)) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitContentTypeHeader(counterVisitor); + assertEquals(32, counterVisitor.getCount(), "CounterPartVisitor count for visitContentTypeHeader should be equal to " + + "CRLF_BYTES length + CONTENT_TYPE_BYTES length + contentType length when charset is not specified"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitTransferEncodingHeader() { + TestFileLikePart fileLikePart = new TestFileLikePart(null, null, null, null, "transferEncoding"); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, EMPTY_BYTE_ARRAY)) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitTransferEncodingHeader(counterVisitor); + assertEquals(45, counterVisitor.getCount(), "CounterPartVisitor count for visitTransferEncodingHeader should be equal to " + + "CRLF_BYTES length + CONTENT_TRANSFER_ENCODING_BYTES length + transferEncoding length"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitContentIdHeader() { + TestFileLikePart fileLikePart = new TestFileLikePart(null, null, null, "contentId"); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, EMPTY_BYTE_ARRAY)) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitContentIdHeader(counterVisitor); + assertEquals(23, counterVisitor.getCount(), "CounterPartVisitor count for visitContentIdHeader should be equal to" + + "CRLF_BYTES length + CONTENT_ID_BYTES length + contentId length"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitCustomHeadersWhenNoCustomHeaders() { + TestFileLikePart fileLikePart = new TestFileLikePart(null); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, EMPTY_BYTE_ARRAY)) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitCustomHeaders(counterVisitor); + assertEquals(0, counterVisitor.getCount(), "CounterPartVisitor count for visitCustomHeaders should be zero for visitCustomHeaders " + + "when there are no custom headers"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitCustomHeaders() { + TestFileLikePart fileLikePart = new TestFileLikePart(null); + fileLikePart.addCustomHeader("custom-header", "header-value"); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, EMPTY_BYTE_ARRAY)) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitCustomHeaders(counterVisitor); + assertEquals(29, counterVisitor.getCount(), "CounterPartVisitor count for visitCustomHeaders should include the length of the custom headers"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitEndOfHeaders() { + TestFileLikePart fileLikePart = new TestFileLikePart(null); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, EMPTY_BYTE_ARRAY)) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitEndOfHeaders(counterVisitor); + assertEquals(4, counterVisitor.getCount(), "CounterPartVisitor count for visitEndOfHeaders should be equal to 4"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitPreContent() { + TestFileLikePart fileLikePart = new TestFileLikePart("Name", "application/test", UTF_8, "contentId", "transferEncoding", "fileName"); + fileLikePart.addCustomHeader("custom-header", "header-value"); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, EMPTY_BYTE_ARRAY)) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitPreContent(counterVisitor); + assertEquals(216, counterVisitor.getCount(), "CounterPartVisitor count for visitPreContent should " + "be equal to the sum of the lengths of precontent"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testVisitPostContents() { + TestFileLikePart fileLikePart = new TestFileLikePart(null); + try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, EMPTY_BYTE_ARRAY)) { + CounterPartVisitor counterVisitor = new CounterPartVisitor(); + multipartPart.visitPostContent(counterVisitor); + assertEquals(2, counterVisitor.getCount(), "CounterPartVisitor count for visitPostContent should be equal to 2"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void transferToShouldWriteStringPart() throws Exception { + String text = FileUtils.readFileToString(TestUtils.resourceAsFile("test_sample_message.eml"), UTF_8); + + List parts = new ArrayList<>(); + parts.add(new StringPart("test_sample_message.eml", text)); + + HttpHeaders headers = new DefaultHttpHeaders(); + headers.set("Cookie", + "open-xchange-public-session-d41d8cd98f00b204e9800998ecf8427e=bfb98150b24f42bd844fc9ef2a9eaad5; open-xchange-secret-TSlq4Cm4nCBnDpBL1Px2A=9a49b76083e34c5ba2ef5c47362313fd; JSESSIONID=6883138728830405130.OX2"); + headers.set("Content-Length", "9241"); + headers.set("Content-Type", "multipart/form-data; boundary=5gigAKQyqDCVdlZ1fCkeLlEDDauTNoOOEhRnFg"); + headers.set("Host", "appsuite.qa.open-xchange.com"); + headers.set("Accept", "*/*"); + + String boundary = "uwyqQolZaSmme019O2kFKvAeHoC14Npp"; + + List> multipartParts = MultipartUtils.generateMultipartParts(parts, boundary.getBytes()); + try (MultipartBody multipartBody = new MultipartBody(multipartParts, "multipart/form-data; boundary=" + boundary, boundary.getBytes())) { + + ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(8 * 1024); + multipartBody.transferTo(byteBuf); + try { + byteBuf.toString(UTF_8); + } finally { + byteBuf.release(); + } + } + } + + /** + * Concrete implementation of {@link FileLikePart} for use in unit tests + */ + private static class TestFileLikePart extends FileLikePart { + + TestFileLikePart(String name) { + this(name, null, null, null, null); + } + + TestFileLikePart(String name, String contentType) { + this(name, contentType, null); + } + + TestFileLikePart(String name, String contentType, Charset charset) { + this(name, contentType, charset, null); + } + + TestFileLikePart(String name, String contentType, Charset charset, String contentId) { + this(name, contentType, charset, contentId, null); + } + + TestFileLikePart(String name, String contentType, Charset charset, String contentId, String transferEncoding) { + this(name, contentType, charset, contentId, transferEncoding, null); + } + + TestFileLikePart(String name, String contentType, Charset charset, String contentId, String transferEncoding, String fileName) { + super(name, contentType, charset, fileName, contentId, transferEncoding); + } + } + + /** + * Concrete implementation of MultipartPart for use in unit tests. + */ + private static class TestMultipartPart extends FileLikeMultipartPart { + + TestMultipartPart(TestFileLikePart part, byte[] boundary) { + super(part, boundary); + } + + @Override + protected long getContentLength() { + return 0; + } + + @Override + protected long transferContentTo(ByteBuf target) { + return 0; + } + + @Override + protected long transferContentTo(WritableByteChannel target) { + return 0; + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/spnego/SpnegoEngineTest.java b/client/src/test/java/org/asynchttpclient/spnego/SpnegoEngineTest.java new file mode 100644 index 0000000000..523ca40c84 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/spnego/SpnegoEngineTest.java @@ -0,0 +1,195 @@ +/* + * 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.spnego; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.apache.commons.io.FileUtils; +import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer; +import org.asynchttpclient.AbstractBasicTest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SpnegoEngineTest extends AbstractBasicTest { + private SimpleKdcServer kerbyServer; + + private String basedir; + private String alice; + private String bob; + private File aliceKeytab; + private File bobKeytab; + private File loginConfig; + + @BeforeEach + public void startServers() throws Exception { + basedir = System.getProperty("basedir"); + if (basedir == null) { + basedir = new File(".").getCanonicalPath(); + } + + // System.setProperty("sun.security.krb5.debug", "true"); + System.setProperty("java.security.krb5.conf", + new File(basedir + File.separator + "target" + File.separator + "krb5.conf").getCanonicalPath()); + loginConfig = new File(basedir + File.separator + "target" + File.separator + "kerberos.jaas"); + System.setProperty("java.security.auth.login.config", loginConfig.getCanonicalPath()); + + kerbyServer = new SimpleKdcServer(); + + kerbyServer.setKdcRealm("service.ws.apache.org"); + kerbyServer.setAllowUdp(false); + kerbyServer.setWorkDir(new File(basedir, "target")); + + //kerbyServer.setInnerKdcImpl(new NettyKdcServerImpl(kerbyServer.getKdcSetting())); + + kerbyServer.init(); + + // Create principals + alice = "alice@service.ws.apache.org"; + bob = "bob/service.ws.apache.org@service.ws.apache.org"; + + kerbyServer.createPrincipal(alice, "alice"); + kerbyServer.createPrincipal(bob, "bob"); + + aliceKeytab = new File(basedir + File.separator + "target" + File.separator + "alice.keytab"); + bobKeytab = new File(basedir + File.separator + "target" + File.separator + "bob.keytab"); + kerbyServer.exportPrincipal(alice, aliceKeytab); + kerbyServer.exportPrincipal(bob, bobKeytab); + + kerbyServer.start(); + + FileUtils.copyInputStreamToFile(SpnegoEngine.class.getResourceAsStream("/kerberos.jaas"), loginConfig); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSpnegoGenerateTokenWithUsernamePassword() throws Exception { + SpnegoEngine spnegoEngine = new SpnegoEngine("alice", + "alice", + "bob", + "service.ws.apache.org", + false, + null, + "alice", + null); + String token = spnegoEngine.generateToken("localhost"); + assertNotNull(token); + assertTrue(token.startsWith("YII")); + } + + @Test + public void testSpnegoGenerateTokenWithNullPasswordFail() { + SpnegoEngine spnegoEngine = new SpnegoEngine("alice", + null, + "bob", + "service.ws.apache.org", + false, + null, + "alice", + null); + assertThrows(SpnegoEngineException.class, () -> spnegoEngine.generateToken("localhost"), "No password provided"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSpnegoGenerateTokenWithUsernamePasswordFail() throws Exception { + SpnegoEngine spnegoEngine = new SpnegoEngine("alice", + "wrong password", + "bob", + "service.ws.apache.org", + false, + null, + "alice", + null); + assertThrows(SpnegoEngineException.class, () -> spnegoEngine.generateToken("localhost")); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSpnegoGenerateTokenWithCustomLoginConfig() throws Exception { + Map loginConfig = new HashMap<>(); + loginConfig.put("useKeyTab", "true"); + loginConfig.put("storeKey", "true"); + loginConfig.put("refreshKrb5Config", "true"); + loginConfig.put("keyTab", aliceKeytab.getCanonicalPath()); + loginConfig.put("principal", alice); + loginConfig.put("debug", String.valueOf(true)); + SpnegoEngine spnegoEngine = new SpnegoEngine(null, + null, + "bob", + "service.ws.apache.org", + false, + loginConfig, + null, + null); + + String token = spnegoEngine.generateToken("localhost"); + assertNotNull(token); + assertTrue(token.startsWith("YII")); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetCompleteServicePrincipalName() throws Exception { + { + SpnegoEngine spnegoEngine = new SpnegoEngine(null, + null, + "bob", + "service.ws.apache.org", + false, + null, + null, + null); + assertEquals("bob@service.ws.apache.org", spnegoEngine.getCompleteServicePrincipalName("localhost")); + } + { + SpnegoEngine spnegoEngine = new SpnegoEngine(null, + null, + null, + "service.ws.apache.org", + true, + null, + null, + null); + assertTrue(spnegoEngine.getCompleteServicePrincipalName("localhost").startsWith("HTTP@")); + } + { + SpnegoEngine spnegoEngine = new SpnegoEngine(null, + null, + null, + "service.ws.apache.org", + false, + null, + null, + null); + assertEquals("HTTP@localhost", spnegoEngine.getCompleteServicePrincipalName("localhost")); + } + } + + @AfterEach + public void cleanup() throws Exception { + if (kerbyServer != null) { + kerbyServer.stop(); + } + FileUtils.deleteQuietly(aliceKeytab); + FileUtils.deleteQuietly(bobKeytab); + FileUtils.deleteQuietly(loginConfig); + } +} diff --git a/client/src/test/java/org/asynchttpclient/test/EchoHandler.java b/client/src/test/java/org/asynchttpclient/test/EchoHandler.java new file mode 100644 index 0000000000..2005cfb5fb --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/test/EchoHandler.java @@ -0,0 +1,173 @@ +/* + * 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.test; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.zip.Deflater; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_ENCODING; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static io.netty.handler.codec.http.HttpHeaderNames.TRANSFER_ENCODING; +import static io.netty.handler.codec.http.HttpHeaderValues.CHUNKED; +import static io.netty.handler.codec.http.HttpHeaderValues.DEFLATE; + +public class EchoHandler extends AbstractHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(EchoHandler.class); + + @Override + public void handle(String pathInContext, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { + + LOGGER.debug("Echo received request {} on path {}", request, pathInContext); + + if (httpRequest.getHeader("X-HEAD") != null) { + httpResponse.setContentLength(1); + } + + if (httpRequest.getHeader("X-ISO") != null) { + httpResponse.setContentType(TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_ISO_8859_1_CHARSET); + } else { + httpResponse.setContentType(TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + } + + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + httpResponse.addHeader("Allow", "GET,HEAD,POST,OPTIONS,TRACE"); + } + + Enumeration e = httpRequest.getHeaderNames(); + String headerName; + while (e.hasMoreElements()) { + headerName = e.nextElement(); + if (headerName.startsWith("LockThread")) { + final int sleepTime = httpRequest.getIntHeader(headerName); + try { + Thread.sleep(sleepTime == -1 ? 40 : sleepTime * 1000L); + } catch (InterruptedException ex) { + // + } + } + + if (headerName.startsWith("X-redirect")) { + httpResponse.sendRedirect(httpRequest.getHeader("X-redirect")); + return; + } + if (headerName.startsWith("X-fail")) { + byte[] body = "custom error message".getBytes(StandardCharsets.US_ASCII); + httpResponse.addHeader(CONTENT_LENGTH.toString(), String.valueOf(body.length)); + httpResponse.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED); + httpResponse.getOutputStream().write(body); + httpResponse.getOutputStream().flush(); + httpResponse.getOutputStream().close(); + return; + } + httpResponse.addHeader("X-" + headerName, httpRequest.getHeader(headerName)); + } + + String pathInfo = httpRequest.getPathInfo(); + if (pathInfo != null) { + httpResponse.addHeader("X-pathInfo", pathInfo); + } + + String queryString = httpRequest.getQueryString(); + if (queryString != null) { + httpResponse.addHeader("X-queryString", queryString); + } + + httpResponse.addHeader("X-KEEP-ALIVE", httpRequest.getRemoteAddr() + ':' + httpRequest.getRemotePort()); + + Cookie[] cs = httpRequest.getCookies(); + if (cs != null) { + for (Cookie c : cs) { + httpResponse.addCookie(c); + } + } + + Enumeration i = httpRequest.getParameterNames(); + if (i.hasMoreElements()) { + StringBuilder requestBody = new StringBuilder(); + while (i.hasMoreElements()) { + headerName = i.nextElement(); + httpResponse.addHeader("X-" + headerName, httpRequest.getParameter(headerName)); + requestBody.append(headerName); + requestBody.append('_'); + } + + if (requestBody.length() > 0) { + String body = requestBody.toString(); + httpResponse.getOutputStream().write(body.getBytes()); + } + } + + if (httpRequest.getHeader("X-COMPRESS") != null) { + byte[] compressed = deflate(IOUtils.toByteArray(httpRequest.getInputStream())); + httpResponse.addIntHeader(CONTENT_LENGTH.toString(), compressed.length); + httpResponse.addHeader(CONTENT_ENCODING.toString(), DEFLATE.toString()); + httpResponse.getOutputStream().write(compressed, 0, compressed.length); + + } else { + httpResponse.addHeader(TRANSFER_ENCODING.toString(), CHUNKED.toString()); + int size = 16384; + if (httpRequest.getContentLength() > 0) { + size = httpRequest.getContentLength(); + } + if (size > 0) { + int read = 0; + while (read > -1) { + byte[] bytes = new byte[size]; + read = httpRequest.getInputStream().read(bytes); + if (read > 0) { + httpResponse.getOutputStream().write(bytes, 0, read); + } + } + } + } + + request.setHandled(true); + httpResponse.getOutputStream().flush(); + // FIXME don't always close, depends on the test, cf ReactiveStreamsTest + httpResponse.getOutputStream().close(); + } + + private static byte[] deflate(byte[] input) throws IOException { + Deflater compressor = new Deflater(); + compressor.setLevel(Deflater.BEST_COMPRESSION); + + compressor.setInput(input); + compressor.finish(); + + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length)) { + byte[] buf = new byte[1024]; + while (!compressor.finished()) { + int count = compressor.deflate(buf); + bos.write(buf, 0, count); + } + return bos.toByteArray(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java b/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java new file mode 100644 index 0000000000..2568b7ae11 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java @@ -0,0 +1,171 @@ +/* + * 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. + * 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.test; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http.HttpHeaders; +import org.asynchttpclient.AsyncCompletionHandlerBase; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.Response; +import org.asynchttpclient.netty.request.NettyRequest; + +import javax.net.ssl.SSLSession; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +public class EventCollectingHandler extends AsyncCompletionHandlerBase { + + public static final String COMPLETED_EVENT = "Completed"; + public static final String STATUS_RECEIVED_EVENT = "StatusReceived"; + public static final String HEADERS_RECEIVED_EVENT = "HeadersReceived"; + public static final String HEADERS_WRITTEN_EVENT = "HeadersWritten"; + private static final String CONTENT_WRITTEN_EVENT = "ContentWritten"; + private static final String CONNECTION_OPEN_EVENT = "ConnectionOpen"; + private static final String HOSTNAME_RESOLUTION_EVENT = "HostnameResolution"; + private static final String HOSTNAME_RESOLUTION_SUCCESS_EVENT = "HostnameResolutionSuccess"; + private static final String HOSTNAME_RESOLUTION_FAILURE_EVENT = "HostnameResolutionFailure"; + private static final String CONNECTION_SUCCESS_EVENT = "ConnectionSuccess"; + private static final String CONNECTION_FAILURE_EVENT = "ConnectionFailure"; + private static final String TLS_HANDSHAKE_EVENT = "TlsHandshake"; + private static final String TLS_HANDSHAKE_SUCCESS_EVENT = "TlsHandshakeSuccess"; + private static final String TLS_HANDSHAKE_FAILURE_EVENT = "TlsHandshakeFailure"; + public static final String CONNECTION_POOL_EVENT = "ConnectionPool"; + public static final String CONNECTION_POOLED_EVENT = "ConnectionPooled"; + public static final String CONNECTION_OFFER_EVENT = "ConnectionOffer"; + public static final String REQUEST_SEND_EVENT = "RequestSend"; + private static final String RETRY_EVENT = "Retry"; + + public Queue firedEvents = new ConcurrentLinkedQueue<>(); + private final CountDownLatch completionLatch = new CountDownLatch(1); + + public void waitForCompletion(int timeout, TimeUnit unit) throws InterruptedException { + if (!completionLatch.await(timeout, unit)) { + fail("Timeout out"); + } + } + + @Override + public Response onCompleted(Response response) throws Exception { + firedEvents.add(COMPLETED_EVENT); + try { + return super.onCompleted(response); + } finally { + completionLatch.countDown(); + } + } + + @Override + public State onStatusReceived(HttpResponseStatus status) throws Exception { + firedEvents.add(STATUS_RECEIVED_EVENT); + return super.onStatusReceived(status); + } + + @Override + public State onHeadersReceived(HttpHeaders headers) throws Exception { + firedEvents.add(HEADERS_RECEIVED_EVENT); + return super.onHeadersReceived(headers); + } + + @Override + public State onHeadersWritten() { + firedEvents.add(HEADERS_WRITTEN_EVENT); + return super.onHeadersWritten(); + } + + @Override + public State onContentWritten() { + firedEvents.add(CONTENT_WRITTEN_EVENT); + return super.onContentWritten(); + } + + @Override + public void onTcpConnectAttempt(InetSocketAddress address) { + firedEvents.add(CONNECTION_OPEN_EVENT); + } + + @Override + public void onTcpConnectSuccess(InetSocketAddress address, Channel connection) { + firedEvents.add(CONNECTION_SUCCESS_EVENT); + } + + @Override + public void onTcpConnectFailure(InetSocketAddress address, Throwable t) { + firedEvents.add(CONNECTION_FAILURE_EVENT); + } + + @Override + public void onHostnameResolutionAttempt(String name) { + firedEvents.add(HOSTNAME_RESOLUTION_EVENT); + } + + @Override + public void onHostnameResolutionSuccess(String name, List addresses) { + firedEvents.add(HOSTNAME_RESOLUTION_SUCCESS_EVENT); + } + + @Override + public void onHostnameResolutionFailure(String name, Throwable cause) { + firedEvents.add(HOSTNAME_RESOLUTION_FAILURE_EVENT); + } + + @Override + public void onTlsHandshakeAttempt() { + firedEvents.add(TLS_HANDSHAKE_EVENT); + } + + @Override + public void onTlsHandshakeSuccess(SSLSession sslSession) { + assertNotNull(sslSession); + firedEvents.add(TLS_HANDSHAKE_SUCCESS_EVENT); + } + + @Override + public void onTlsHandshakeFailure(Throwable cause) { + firedEvents.add(TLS_HANDSHAKE_FAILURE_EVENT); + } + + @Override + public void onConnectionPoolAttempt() { + firedEvents.add(CONNECTION_POOL_EVENT); + } + + @Override + public void onConnectionPooled(Channel connection) { + firedEvents.add(CONNECTION_POOLED_EVENT); + } + + @Override + public void onConnectionOffer(Channel connection) { + firedEvents.add(CONNECTION_OFFER_EVENT); + } + + @Override + public void onRequestSend(NettyRequest request) { + firedEvents.add(REQUEST_SEND_EVENT); + } + + @Override + public void onRetry() { + firedEvents.add(RETRY_EVENT); + } +} diff --git a/client/src/test/java/org/asynchttpclient/test/Slf4jJuliLog.java b/client/src/test/java/org/asynchttpclient/test/Slf4jJuliLog.java new file mode 100644 index 0000000000..14b8527715 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/test/Slf4jJuliLog.java @@ -0,0 +1,125 @@ +/* + * 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.test; + +import org.apache.juli.logging.Log; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Slf4jJuliLog implements Log { + + private final Logger logger; + + // just so that ServiceLoader doesn't crash, unused + public Slf4jJuliLog() { + logger = null; + } + + // actual constructor + public Slf4jJuliLog(String name) { + logger = LoggerFactory.getLogger(name); + } + + @Override + public void debug(Object arg0) { + logger.debug(arg0.toString()); + } + + @Override + public void debug(Object arg0, Throwable arg1) { + logger.debug(arg0.toString(), arg1); + } + + @Override + public void error(Object arg0) { + logger.error(arg0.toString()); + } + + @Override + public void error(Object arg0, Throwable arg1) { + logger.error(arg0.toString(), arg1); + } + + @Override + public void fatal(Object arg0) { + logger.error(arg0.toString()); + } + + @Override + public void fatal(Object arg0, Throwable arg1) { + logger.error(arg0.toString(), arg1); + } + + @Override + public void info(Object arg0) { + logger.info(arg0.toString()); + } + + @Override + public void info(Object arg0, Throwable arg1) { + logger.info(arg0.toString(), arg1); + } + + @Override + public boolean isDebugEnabled() { + return logger.isDebugEnabled(); + } + + @Override + public boolean isErrorEnabled() { + return logger.isErrorEnabled(); + } + + @Override + public boolean isFatalEnabled() { + return logger.isErrorEnabled(); + } + + @Override + public boolean isInfoEnabled() { + return logger.isInfoEnabled(); + } + + @Override + public boolean isTraceEnabled() { + return logger.isTraceEnabled(); + } + + @Override + public boolean isWarnEnabled() { + return logger.isWarnEnabled(); + } + + @Override + public void trace(Object arg0) { + logger.trace(arg0.toString()); + } + + @Override + public void trace(Object arg0, Throwable arg1) { + logger.trace(arg0.toString(), arg1); + } + + @Override + public void warn(Object arg0) { + logger.warn(arg0.toString()); + } + + @Override + public void warn(Object arg0, Throwable arg1) { + logger.warn(arg0.toString(), arg1); + } +} diff --git a/client/src/test/java/org/asynchttpclient/test/TestUtils.java b/client/src/test/java/org/asynchttpclient/test/TestUtils.java new file mode 100644 index 0000000000..4995628245 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/test/TestUtils.java @@ -0,0 +1,385 @@ +/* + * 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.test; + +import io.netty.handler.codec.http.HttpHeaders; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.io.FileUtils; +import org.asynchttpclient.AsyncCompletionHandler; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.Response; +import org.asynchttpclient.SslEngineFactory; +import org.asynchttpclient.netty.ssl.JsseSslEngineFactory; +import org.asynchttpclient.util.MessageDigestUtils; +import org.eclipse.jetty.security.ConstraintMapping; +import org.eclipse.jetty.security.ConstraintSecurityHandler; +import org.eclipse.jetty.security.HashLoginService; +import org.eclipse.jetty.security.LoginService; +import org.eclipse.jetty.security.authentication.BasicAuthenticator; +import org.eclipse.jetty.security.authentication.DigestAuthenticator; +import org.eclipse.jetty.security.authentication.LoginAuthenticator; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import javax.net.ServerSocketFactory; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public final class TestUtils { + + public static final int TIMEOUT = 30; + public static final String USER = "user"; + public static final String ADMIN = "admin"; + public static final String TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET = "text/html;charset=UTF-8"; + public static final String TEXT_HTML_CONTENT_TYPE_WITH_ISO_8859_1_CHARSET = "text/html;charset=ISO-8859-1"; + public static final File TMP_DIR = new File(System.getProperty("java.io.tmpdir"), "ahc-tests-" + UUID.randomUUID().toString().substring(0, 8)); + private static final byte[] PATTERN_BYTES = "FooBarBazQixFooBarBazQixFooBarBazQixFooBarBazQixFooBarBazQixFooBarBazQix".getBytes(StandardCharsets.UTF_16); + public static final File LARGE_IMAGE_FILE; + public static final byte[] LARGE_IMAGE_BYTES; + public static final String LARGE_IMAGE_BYTES_MD5; + public static final File SIMPLE_TEXT_FILE; + public static final String SIMPLE_TEXT_FILE_STRING; + private static final LoginService LOGIN_SERVICE = new HashLoginService("MyRealm", "src/test/resources/realm.properties"); + + static { + try { + TMP_DIR.mkdirs(); + TMP_DIR.deleteOnExit(); + LARGE_IMAGE_FILE = resourceAsFile("300k.png"); + LARGE_IMAGE_BYTES = FileUtils.readFileToByteArray(LARGE_IMAGE_FILE); + LARGE_IMAGE_BYTES_MD5 = md5(LARGE_IMAGE_BYTES); + SIMPLE_TEXT_FILE = resourceAsFile("SimpleTextFile.txt"); + SIMPLE_TEXT_FILE_STRING = FileUtils.readFileToString(SIMPLE_TEXT_FILE, UTF_8); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + private TestUtils() { + } + + public static synchronized int findFreePort() throws IOException { + try (ServerSocket socket = ServerSocketFactory.getDefault().createServerSocket(0)) { + return socket.getLocalPort(); + } + } + + public static File resourceAsFile(String path) throws URISyntaxException, IOException { + ClassLoader cl = TestUtils.class.getClassLoader(); + URI uri = cl.getResource(path).toURI(); + if (uri.isAbsolute() && !uri.isOpaque()) { + return new File(uri); + } else { + File tmpFile = File.createTempFile("tmpfile-", ".data", TMP_DIR); + tmpFile.deleteOnExit(); + try (InputStream is = cl.getResourceAsStream(path)) { + FileUtils.copyInputStreamToFile(is, tmpFile); + return tmpFile; + } + } + } + + public static File createTempFile(int approxSize) throws IOException { + long repeats = approxSize / PATTERN_BYTES.length + 1; + File tmpFile = File.createTempFile("tmpfile-", ".data", TMP_DIR); + tmpFile.deleteOnExit(); + try (OutputStream out = Files.newOutputStream(tmpFile.toPath())) { + for (int i = 0; i < repeats; i++) { + out.write(PATTERN_BYTES); + } + + long expectedFileSize = PATTERN_BYTES.length * repeats; + assertEquals(expectedFileSize, tmpFile.length(), "Invalid file length"); + + return tmpFile; + } + } + + public static ServerConnector addHttpConnector(Server server) { + ServerConnector connector = new ServerConnector(server); + server.addConnector(connector); + return connector; + } + + public static ServerConnector addHttpsConnector(Server server) throws IOException, URISyntaxException { + String keyStoreFile = resourceAsFile("ssltest-keystore.jks").getAbsolutePath(); + SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); + sslContextFactory.setKeyStorePath(keyStoreFile); + sslContextFactory.setKeyStorePassword("changeit"); + + String trustStoreFile = resourceAsFile("ssltest-cacerts.jks").getAbsolutePath(); + sslContextFactory.setTrustStorePath(trustStoreFile); + sslContextFactory.setTrustStorePassword("changeit"); + + HttpConfiguration httpsConfig = new HttpConfiguration(); + httpsConfig.setSecureScheme("https"); + httpsConfig.addCustomizer(new SecureRequestCustomizer()); + + ServerConnector connector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"), new HttpConnectionFactory(httpsConfig)); + + server.addConnector(connector); + return connector; + } + + public static void addBasicAuthHandler(Server server, Handler handler) { + addAuthHandler(server, Constraint.__BASIC_AUTH, new BasicAuthenticator(), handler); + } + + public static void addDigestAuthHandler(Server server, Handler handler) { + addAuthHandler(server, Constraint.__DIGEST_AUTH, new DigestAuthenticator(), handler); + } + + private static void addAuthHandler(Server server, String auth, LoginAuthenticator authenticator, Handler handler) { + server.addBean(LOGIN_SERVICE); + + Constraint constraint = new Constraint(); + constraint.setName(auth); + constraint.setRoles(new String[]{USER, ADMIN}); + constraint.setAuthenticate(true); + + ConstraintMapping mapping = new ConstraintMapping(); + mapping.setConstraint(constraint); + mapping.setPathSpec("/*"); + + Set knownRoles = new HashSet<>(); + knownRoles.add(USER); + knownRoles.add(ADMIN); + + List cm = new ArrayList<>(); + cm.add(mapping); + + ConstraintSecurityHandler security = new ConstraintSecurityHandler(); + security.setConstraintMappings(cm, knownRoles); + security.setAuthenticator(authenticator); + security.setLoginService(LOGIN_SERVICE); + security.setHandler(handler); + server.setHandler(security); + } + + private static KeyManager[] createKeyManagers() throws GeneralSecurityException, IOException { + KeyStore ks = KeyStore.getInstance("JKS"); + try (InputStream keyStoreStream = TestUtils.class.getClassLoader().getResourceAsStream("ssltest-cacerts.jks")) { + char[] keyStorePassword = "changeit".toCharArray(); + ks.load(keyStoreStream, keyStorePassword); + } + assert ks.size() > 0; + + // Set up key manager factory to use our key store + char[] certificatePassword = "changeit".toCharArray(); + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, certificatePassword); + + // Initialize the SSLContext to work with our key managers. + return kmf.getKeyManagers(); + } + + private static TrustManager[] createTrustManagers() throws GeneralSecurityException, IOException { + KeyStore ks = KeyStore.getInstance("JKS"); + try (InputStream keyStoreStream = TestUtils.class.getClassLoader().getResourceAsStream("ssltest-keystore.jks")) { + char[] keyStorePassword = "changeit".toCharArray(); + ks.load(keyStoreStream, keyStorePassword); + } + assert ks.size() > 0; + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); + return tmf.getTrustManagers(); + } + + public static SslEngineFactory createSslEngineFactory() { + return createSslEngineFactory(new AtomicBoolean(true)); + } + + public static SslEngineFactory createSslEngineFactory(AtomicBoolean trust) { + try { + KeyManager[] keyManagers = createKeyManagers(); + TrustManager[] trustManagers = {dummyTrustManager(trust, (X509TrustManager) createTrustManagers()[0])}; + SecureRandom secureRandom = new SecureRandom(); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagers, trustManagers, secureRandom); + + return new JsseSslEngineFactory(sslContext); + + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + } + + private static TrustManager dummyTrustManager(final AtomicBoolean trust, final X509TrustManager tm) { + return new DummyTrustManager(trust, tm); + + } + + public static File getClasspathFile(String file) throws FileNotFoundException { + ClassLoader cl = null; + try { + cl = Thread.currentThread().getContextClassLoader(); + } catch (Throwable ex) { + // + } + if (cl == null) { + cl = TestUtils.class.getClassLoader(); + } + URL resourceUrl = cl.getResource(file); + + try { + return new File(new URI(resourceUrl.toString()).getSchemeSpecificPart()); + } catch (URISyntaxException e) { + throw new FileNotFoundException(file); + } + } + + public static void assertContentTypesEquals(String actual, String expected) { + assertEquals(actual.replace("; ", "").toLowerCase(Locale.ENGLISH), + expected.replace("; ", "").toLowerCase(Locale.ENGLISH), "Unexpected content-type"); + } + + public static void writeResponseBody(HttpServletResponse response, String body) { + response.setContentLength(body.length()); + try { + response.getOutputStream().print(body); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static String md5(byte[] bytes) { + return md5(bytes, 0, bytes.length); + } + + public static String md5(byte[] bytes, int offset, int len) { + try { + MessageDigest md = MessageDigestUtils.pooledMd5MessageDigest(); + md.update(bytes, offset, len); + return Base64.getEncoder().encodeToString(md.digest()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static class DummyTrustManager implements X509TrustManager { + + private final X509TrustManager tm; + private final AtomicBoolean trust; + + DummyTrustManager(final AtomicBoolean trust, final X509TrustManager tm) { + this.trust = trust; + this.tm = tm; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + tm.checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + if (!trust.get()) { + throw new CertificateException("Server certificate not trusted."); + } + tm.checkServerTrusted(chain, authType); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return tm.getAcceptedIssuers(); + } + } + + public static class AsyncCompletionHandlerAdapter extends AsyncCompletionHandler { + + @Override + public Response onCompleted(Response response) throws Exception { + return response; + } + + @Override + public void onThrowable(Throwable t) { + fail("Unexpected exception: " + t.getMessage(), t); + } + } + + public static class AsyncHandlerAdapter implements AsyncHandler { + + @Override + public void onThrowable(Throwable t) { + fail("Unexpected exception", t); + } + + @Override + public State onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { + return State.CONTINUE; + } + + @Override + public State onStatusReceived(final HttpResponseStatus responseStatus) { + return State.CONTINUE; + } + + @Override + public State onHeadersReceived(final HttpHeaders headers) throws Exception { + return State.CONTINUE; + } + + @Override + public String onCompleted() throws Exception { + return ""; + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/testserver/HttpServer.java b/client/src/test/java/org/asynchttpclient/testserver/HttpServer.java new file mode 100644 index 0000000000..b0848cad31 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/testserver/HttpServer.java @@ -0,0 +1,269 @@ +/* + * 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.testserver; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.server.Handler; +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; + +import java.io.Closeable; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentLinkedQueue; + +import static io.netty.handler.codec.http.HttpHeaderNames.LOCATION; +import static org.asynchttpclient.test.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_ISO_8859_1_CHARSET; +import static org.asynchttpclient.test.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.asynchttpclient.test.TestUtils.addHttpsConnector; + +public class HttpServer implements Closeable { + + private final ConcurrentLinkedQueue handlers = new ConcurrentLinkedQueue<>(); + private int httpPort; + private int httpsPort; + private Server server; + + public HttpServer() { + } + + public HttpServer(int httpPort, int httpsPort) { + this.httpPort = httpPort; + this.httpsPort = httpsPort; + } + + public void start() throws Exception { + server = new Server(); + + ServerConnector httpConnector = addHttpConnector(server); + if (httpPort != 0) { + httpConnector.setPort(httpPort); + } + + server.setHandler(new QueueHandler()); + ServerConnector httpsConnector = addHttpsConnector(server); + if (httpsPort != 0) { + httpsConnector.setPort(httpsPort); + } + + server.start(); + + httpPort = httpConnector.getLocalPort(); + httpsPort = httpsConnector.getLocalPort(); + } + + public void enqueue(Handler handler) { + handlers.offer(handler); + } + + public void enqueueOk() { + enqueueResponse(response -> response.setStatus(200)); + } + + public void enqueueResponse(HttpServletResponseConsumer c) { + handlers.offer(new ConsumerHandler(c)); + } + + public void enqueueEcho() { + handlers.offer(new EchoHandler()); + } + + public void enqueueRedirect(int status, String location) { + enqueueResponse(response -> { + response.setStatus(status); + response.setHeader(LOCATION.toString(), location); + }); + } + + public int getHttpPort() { + return httpPort; + } + + public int getsHttpPort() { + return httpsPort; + } + + public String getHttpUrl() { + return "http://localhost/" + httpPort; + } + + public String getHttpsUrl() { + return "https://localhost/" + httpsPort; + } + + public void reset() { + handlers.clear(); + } + + @Override + public void close() throws IOException { + if (server != null) { + try { + server.stop(); + } catch (Exception e) { + throw new IOException(e); + } + } + } + + @FunctionalInterface + public interface HttpServletResponseConsumer { + + void apply(HttpServletResponse response) throws IOException, ServletException; + } + + public abstract static class AutoFlushHandler extends AbstractHandler { + + private final boolean closeAfterResponse; + + AutoFlushHandler() { + this(false); + } + + AutoFlushHandler(boolean closeAfterResponse) { + this.closeAfterResponse = closeAfterResponse; + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + handle0(target, baseRequest, request, response); + response.getOutputStream().flush(); + if (closeAfterResponse) { + response.getOutputStream().close(); + } + } + + protected abstract void handle0(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; + } + + private static class ConsumerHandler extends AutoFlushHandler { + + private final HttpServletResponseConsumer c; + + ConsumerHandler(HttpServletResponseConsumer c) { + this(c, false); + } + + ConsumerHandler(HttpServletResponseConsumer c, boolean closeAfterResponse) { + super(closeAfterResponse); + this.c = c; + } + + @Override + protected void handle0(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + c.apply(response); + } + } + + public static class EchoHandler extends AutoFlushHandler { + + @Override + protected void handle0(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + + String delay = request.getHeader("X-Delay"); + if (delay != null) { + try { + Thread.sleep(Long.parseLong(delay)); + } catch (NumberFormatException | InterruptedException e1) { + throw new ServletException(e1); + } + } + + response.setStatus(200); + + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + response.addHeader("Allow", "GET,HEAD,POST,OPTIONS,TRACE"); + } + + response.setContentType(request.getHeader("X-IsoCharset") != null ? TEXT_HTML_CONTENT_TYPE_WITH_ISO_8859_1_CHARSET : TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + + response.addHeader("X-ClientPort", String.valueOf(request.getRemotePort())); + + String pathInfo = request.getPathInfo(); + if (pathInfo != null) { + response.addHeader("X-PathInfo", pathInfo); + } + + String queryString = request.getQueryString(); + if (queryString != null) { + response.addHeader("X-QueryString", queryString); + } + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + response.addHeader("X-" + headerName, request.getHeader(headerName)); + } + + StringBuilder requestBody = new StringBuilder(); + for (Entry e : baseRequest.getParameterMap().entrySet()) { + response.addHeader("X-" + e.getKey(), URLEncoder.encode(e.getValue()[0], StandardCharsets.UTF_8)); + } + + Cookie[] cs = request.getCookies(); + if (cs != null) { + for (Cookie c : cs) { + response.addCookie(c); + } + } + + if (requestBody.length() > 0) { + response.getOutputStream().write(requestBody.toString().getBytes()); + } + + int size = 16384; + if (request.getContentLength() > 0) { + size = request.getContentLength(); + } + if (size > 0) { + int read = 0; + while (read > -1) { + byte[] bytes = new byte[size]; + read = request.getInputStream().read(bytes); + if (read > 0) { + response.getOutputStream().write(bytes, 0, read); + } + } + } + } + } + + private class QueueHandler extends AbstractHandler { + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + + Handler handler = handlers.poll(); + if (handler == null) { + response.sendError(500, "No handler enqueued"); + response.getOutputStream().flush(); + response.getOutputStream().close(); + + } else { + handler.handle(target, baseRequest, request, response); + } + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/testserver/HttpTest.java b/client/src/test/java/org/asynchttpclient/testserver/HttpTest.java new file mode 100644 index 0000000000..b41b6ab1b6 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/testserver/HttpTest.java @@ -0,0 +1,104 @@ +/* + * 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.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"; + protected static final String STATUS_RECEIVED_EVENT = "StatusReceived"; + protected static final String HEADERS_RECEIVED_EVENT = "HeadersReceived"; + protected static final String HEADERS_WRITTEN_EVENT = "HeadersWritten"; + protected static final String CONNECTION_OPEN_EVENT = "ConnectionOpen"; + protected static final String HOSTNAME_RESOLUTION_EVENT = "HostnameResolution"; + protected static final String HOSTNAME_RESOLUTION_SUCCESS_EVENT = "HostnameResolutionSuccess"; + protected static final String CONNECTION_SUCCESS_EVENT = "ConnectionSuccess"; + protected static final String TLS_HANDSHAKE_EVENT = "TlsHandshake"; + protected static final String TLS_HANDSHAKE_SUCCESS_EVENT = "TlsHandshakeSuccess"; + protected static final String CONNECTION_POOL_EVENT = "ConnectionPool"; + protected static final String CONNECTION_OFFER_EVENT = "ConnectionOffer"; + protected static final String REQUEST_SEND_EVENT = "RequestSend"; + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + protected ClientTestBody withClient() { + return withClient(config().setMaxRedirects(0)); + } + + protected ClientTestBody withClient(DefaultAsyncHttpClientConfig.Builder builder) { + return withClient(builder.build()); + } + + private ClientTestBody withClient(AsyncHttpClientConfig config) { + return new ClientTestBody(config); + } + + protected ServerTestBody withServer(HttpServer server) { + return new ServerTestBody(server); + } + + @FunctionalInterface + protected interface ClientFunction { + void apply(AsyncHttpClient client) throws Throwable; + } + + @FunctionalInterface + protected interface ServerFunction { + void apply(HttpServer server) throws Throwable; + } + + protected static class ClientTestBody { + + private final AsyncHttpClientConfig config; + + private ClientTestBody(AsyncHttpClientConfig config) { + this.config = config; + } + + public void run(ClientFunction f) throws Throwable { + try (AsyncHttpClient client = asyncHttpClient(config)) { + f.apply(client); + } + } + } + + protected static class ServerTestBody { + + private final HttpServer server; + + private ServerTestBody(HttpServer server) { + this.server = server; + } + + public void run(ServerFunction f) throws Throwable { + try { + f.apply(server); + } finally { + server.reset(); + } + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/testserver/SocksProxy.java b/client/src/test/java/org/asynchttpclient/testserver/SocksProxy.java new file mode 100644 index 0000000000..b8e67b79f5 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/testserver/SocksProxy.java @@ -0,0 +1,203 @@ +/* + * SOCKS Proxy in JAVA + * By Gareth Owen + * drgowen@gmail.com + * MIT Licence + */ + +package org.asynchttpclient.testserver; + +// NOTES : LISTENS ON PORT 8000 + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Set; + +public class SocksProxy { + + private static final ArrayList clients = new ArrayList<>(); + + public SocksProxy(int runningTime) throws IOException { + ServerSocketChannel socks = ServerSocketChannel.open(); + socks.socket().bind(new InetSocketAddress(8000)); + socks.configureBlocking(false); + Selector select = Selector.open(); + socks.register(select, SelectionKey.OP_ACCEPT); + + int lastClients = clients.size(); + // select loop + for (long end = System.currentTimeMillis() + runningTime; System.currentTimeMillis() < end; ) { + select.select(5000); + + Set keys = select.selectedKeys(); + for (SelectionKey k : keys) { + + if (!k.isValid()) { + continue; + } + + // new connection? + if (k.isAcceptable() && k.channel() == socks) { + // server socket + SocketChannel csock = socks.accept(); + if (csock == null) { + continue; + } + addClient(csock); + csock.register(select, SelectionKey.OP_READ); + } else if (k.isReadable()) { + // new data on a client/remote socket + for (int i = 0; i < clients.size(); i++) { + 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) + cl.newRemoteData(); + } + } catch (IOException e) { // error occurred - remove client + cl.client.close(); + if (cl.remote != null) { + cl.remote.close(); + } + k.cancel(); + clients.remove(cl); + } + + } + } + } + + // client timeout check + for (int i = 0; i < clients.size(); i++) { + SocksClient cl = clients.get(i); + if (System.currentTimeMillis() - cl.lastData > 30000L) { + cl.client.close(); + if (cl.remote != null) { + cl.remote.close(); + } + clients.remove(cl); + } + } + if (clients.size() != lastClients) { + System.out.println(clients.size()); + lastClients = clients.size(); + } + } + } + + // utility function + private void addClient(SocketChannel s) { + SocksClient cl; + try { + cl = new SocksClient(s); + } catch (IOException e) { + e.printStackTrace(); + return; + } + clients.add(cl); + } + + // socks client class - one per client connection + class SocksClient { + SocketChannel client, remote; + boolean connected; + long lastData; + + SocksClient(SocketChannel c) throws IOException { + client = c; + client.configureBlocking(false); + lastData = System.currentTimeMillis(); + } + + void newRemoteData() throws IOException { + ByteBuffer buf = ByteBuffer.allocate(1024); + if (remote.read(buf) == -1) { + throw new IOException("disconnected"); + } + lastData = System.currentTimeMillis(); + buf.flip(); + client.write(buf); + } + + void newClientData(Selector selector) throws IOException { + if (!connected) { + ByteBuffer inbuf = ByteBuffer.allocate(512); + if (client.read(inbuf) < 1) { + return; + } + inbuf.flip(); + + // read socks header + int ver = inbuf.get(); + if (ver != 4) { + throw new IOException("incorrect version" + ver); + } + int cmd = inbuf.get(); + + // check supported command + if (cmd != 1) { + throw new IOException("incorrect version"); + } + + final int port = inbuf.getShort() & 0xffff; + + final byte[] ip = new byte[4]; + // fetch IP + inbuf.get(ip); + + InetAddress remoteAddr = InetAddress.getByAddress(ip); + + while (inbuf.get() != 0) { + ; // username + } + + // hostname provided, not IP + if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] != 0) { // host provided + StringBuilder host = new StringBuilder(); + byte b; + while ((b = inbuf.get()) != 0) { + host.append(b); + } + remoteAddr = InetAddress.getByName(host.toString()); + System.out.println(host.toString() + remoteAddr); + } + + remote = SocketChannel.open(new InetSocketAddress(remoteAddr, port)); + + ByteBuffer out = ByteBuffer.allocate(20); + out.put((byte) 0); + out.put((byte) (remote.isConnected() ? 0x5a : 0x5b)); + out.putShort((short) port); + out.put(remoteAddr.getAddress()); + out.flip(); + client.write(out); + + if (!remote.isConnected()) { + throw new IOException("connect failed"); + } + + remote.configureBlocking(false); + remote.register(selector, SelectionKey.OP_READ); + + connected = true; + } else { + ByteBuffer buf = ByteBuffer.allocate(1024); + 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/UriParserTest.java b/client/src/test/java/org/asynchttpclient/uri/UriParserTest.java new file mode 100644 index 0000000000..1e314f56db --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/uri/UriParserTest.java @@ -0,0 +1,121 @@ +/* + * 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.uri; + +import io.github.artsok.RepeatedIfExceptionsTest; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class UriParserTest { + + private static void assertUriEquals(UriParser parser, URI uri) { + assertEquals(uri.getScheme(), parser.scheme); + assertEquals(uri.getUserInfo(), parser.userInfo); + assertEquals(uri.getHost(), parser.host); + assertEquals(uri.getPort(), parser.port); + assertEquals(uri.getPath(), parser.path); + assertEquals(uri.getQuery(), parser.query); + } + + private static void validateAgainstAbsoluteURI(String url) { + final UriParser parser = UriParser.parse(null, url); + assertUriEquals(parser, URI.create(url)); + } + + private static void validateAgainstRelativeURI(Uri uriContext, String urlContext, String url) { + final UriParser parser = UriParser.parse(uriContext, url); + assertUriEquals(parser, URI.create(urlContext).resolve(URI.create(url))); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testUrlWithPathAndQuery() { + validateAgainstAbsoluteURI("http://example.com:8080/test?q=1"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testFragmentTryingToTrickAuthorityAsBasicAuthCredentials() { + validateAgainstAbsoluteURI("http://1.2.3.4:81/#@5.6.7.8:82/aaa/b?q=xxx"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testUrlHasLeadingAndTrailingWhiteSpace() { + String url = " http://user@example.com:8080/test?q=1 "; + final UriParser parser = UriParser.parse(null, url); + assertUriEquals(parser, URI.create(url.trim())); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testResolveAbsoluteUriAgainstContext() { + Uri context = new Uri("https", null, "example.com", 80, "/path", "", null); + validateAgainstRelativeURI(context, "https://example.com:80/path", "http://example.com/path"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRootRelativePath() { + Uri context = new Uri("https", null, "example.com", 80, "/path", "q=2", null); + validateAgainstRelativeURI(context, "https://example.com:80/path?q=2", "/relativeUrl"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testCurrentDirRelativePath() { + Uri context = new Uri("https", null, "example.com", 80, "/foo/bar", "q=2", null); + validateAgainstRelativeURI(context, "https://example.com:80/foo/bar?q=2", "relativeUrl"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testFragmentOnly() { + Uri context = new Uri("https", null, "example.com", 80, "/path", "q=2", null); + validateAgainstRelativeURI(context, "https://example.com:80/path?q=2", "#test"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeUrlWithQuery() { + Uri context = new Uri("https", null, "example.com", 80, "/path", "q=2", null); + validateAgainstRelativeURI(context, "https://example.com:80/path?q=2", "/relativePath?q=3"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeUrlWithQueryOnly() { + Uri context = new Uri("https", null, "example.com", 80, "/path", "q=2", null); + validateAgainstRelativeURI(context, "https://example.com:80/path?q=2", "?q=3"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeURLWithDots() { + Uri context = new Uri("https", null, "example.com", 80, "/path", "q=2", null); + validateAgainstRelativeURI(context, "https://example.com:80/path?q=2", "./relative/./url"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeURLWithTwoEmbeddedDots() { + Uri context = new Uri("https", null, "example.com", 80, "/path", "q=2", null); + validateAgainstRelativeURI(context, "https://example.com:80/path?q=2", "./relative/../url"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeURLWithTwoTrailingDots() { + Uri context = new Uri("https", null, "example.com", 80, "/path", "q=2", null); + validateAgainstRelativeURI(context, "https://example.com:80/path?q=2", "./relative/url/.."); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeURLWithOneTrailingDot() { + Uri context = new Uri("https", null, "example.com", 80, "/path", "q=2", null); + validateAgainstRelativeURI(context, "https://example.com:80/path?q=2", "./relative/url/."); + } +} diff --git a/client/src/test/java/org/asynchttpclient/uri/UriTest.java b/client/src/test/java/org/asynchttpclient/uri/UriTest.java new file mode 100644 index 0000000000..f766854e13 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/uri/UriTest.java @@ -0,0 +1,355 @@ +/* + * 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. + * 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.uri; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.junit.jupiter.api.Disabled; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class UriTest { + + private static void assertUriEquals(Uri uri, URI javaUri) { + assertEquals(javaUri.getScheme(), uri.getScheme()); + assertEquals(javaUri.getUserInfo(), uri.getUserInfo()); + assertEquals(javaUri.getHost(), uri.getHost()); + assertEquals(javaUri.getPort(), uri.getPort()); + assertEquals(javaUri.getPath(), uri.getPath()); + assertEquals(javaUri.getQuery(), uri.getQuery()); + } + + private static void validateAgainstAbsoluteURI(String url) { + assertUriEquals(Uri.create(url), URI.create(url)); + } + + private static void validateAgainstRelativeURI(String context, String url) { + assertUriEquals(Uri.create(Uri.create(context), url), URI.create(context).resolve(URI.create(url))); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testSimpleParsing() { + validateAgainstAbsoluteURI("https://graph.facebook.com/750198471659552/accounts/test-users?method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRootRelativeURIWithRootContext() { + validateAgainstRelativeURI("https://graph.facebook.com/", "/750198471659552/accounts/test-users?method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRootRelativeURIWithNonRootContext() { + validateAgainstRelativeURI("https://graph.facebook.com/foo/bar", "/750198471659552/accounts/test-users?method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testNonRootRelativeURIWithNonRootContext() { + validateAgainstRelativeURI("https://graph.facebook.com/foo/bar", "750198471659552/accounts/test-users?method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); + } + + @Disabled + @RepeatedIfExceptionsTest(repeats = 5) + // FIXME weird: java.net.URI#getPath return "750198471659552/accounts/test-users" without a "/"?! + public void testNonRootRelativeURIWithRootContext() { + validateAgainstRelativeURI("https://graph.facebook.com/", "750198471659552/accounts/test-users?method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testAbsoluteURIWithContext() { + validateAgainstRelativeURI("https://hello.com/foo/bar", + "https://graph.facebook.com/750198471659552/accounts/test-users?method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeUriWithDots() { + validateAgainstRelativeURI("https://hello.com/level1/level2/", "../other/content/img.png"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeUriWithDotsAboveRoot() { + validateAgainstRelativeURI("https://hello.com/level1", "../other/content/img.png"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeUriWithAbsoluteDots() { + validateAgainstRelativeURI("https://hello.com/level1/", "/../other/content/img.png"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeUriWithConsecutiveDots() { + validateAgainstRelativeURI("https://hello.com/level1/level2/", "../../other/content/img.png"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeUriWithConsecutiveDotsAboveRoot() { + validateAgainstRelativeURI("https://hello.com/level1/level2", "../../other/content/img.png"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeUriWithAbsoluteConsecutiveDots() { + validateAgainstRelativeURI("https://hello.com/level1/level2/", "/../../other/content/img.png"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeUriWithConsecutiveDotsFromRoot() { + validateAgainstRelativeURI("https://hello.com/", "../../../other/content/img.png"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeUriWithConsecutiveDotsFromRootResource() { + validateAgainstRelativeURI("https://hello.com/level1", "../../../other/content/img.png"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeUriWithConsecutiveDotsFromSubrootResource() { + validateAgainstRelativeURI("https://hello.com/level1/level2", "../../../other/content/img.png"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeUriWithConsecutiveDotsFromLevel3Resource() { + validateAgainstRelativeURI("https://hello.com/level1/level2/level3", "../../../other/content/img.png"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testRelativeUriWithNoScheme() { + validateAgainstRelativeURI("https://hello.com/level1", "//world.org/content/img.png"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testCreateAndToUrl() { + String url = "https://hello.com/level1/level2/level3"; + Uri uri = Uri.create(url); + assertEquals(url, uri.toUrl(), "url used to create uri and url returned from toUrl do not match"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testToUrlWithUserInfoPortPathAndQuery() { + Uri uri = new Uri("http", "user", "example.com", 44, "/path/path2", "query=4", null); + assertEquals("http://user@example.com:44/path/path2?query=4", uri.toUrl(), "toUrl returned incorrect url"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testQueryWithNonRootPath() { + Uri uri = Uri.create("http://hello.com/foo?query=value"); + assertEquals("/foo", uri.getPath()); + assertEquals("query=value", uri.getQuery()); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testQueryWithNonRootPathAndTrailingSlash() { + Uri uri = Uri.create("http://hello.com/foo/?query=value"); + assertEquals("/foo/", uri.getPath()); + assertEquals("query=value", uri.getQuery()); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testQueryWithRootPath() { + Uri uri = Uri.create("http://hello.com/?query=value"); + assertEquals("", uri.getPath()); + assertEquals("query=value", uri.getQuery()); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testQueryWithRootPathAndTrailingSlash() { + Uri uri = Uri.create("http://hello.com/?query=value"); + assertEquals("/", uri.getPath()); + assertEquals("query=value", uri.getQuery()); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testWithNewScheme() { + Uri uri = new Uri("http", "user", "example.com", 44, "/path/path2", "query=4", null); + Uri newUri = uri.withNewScheme("https"); + assertEquals("https", newUri.getScheme()); + assertEquals("https://user@example.com:44/path/path2?query=4", newUri.toUrl(), "toUrl returned incorrect url"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testWithNewQuery() { + Uri uri = new Uri("http", "user", "example.com", 44, "/path/path2", "query=4", null); + Uri newUri = uri.withNewQuery("query2=10&query3=20"); + assertEquals(newUri.getQuery(), "query2=10&query3=20"); + assertEquals("http://user@example.com:44/path/path2?query2=10&query3=20", newUri.toUrl(), "toUrl returned incorrect url"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testToRelativeUrl() { + Uri uri = new Uri("http", "user", "example.com", 44, "/path/path2", "query=4", null); + String relativeUrl = uri.toRelativeUrl(); + assertEquals("/path/path2?query=4", relativeUrl, "toRelativeUrl returned incorrect url"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testToRelativeUrlWithEmptyPath() { + Uri uri = new Uri("http", "user", "example.com", 44, null, "query=4", null); + String relativeUrl = uri.toRelativeUrl(); + assertEquals("/?query=4", relativeUrl, "toRelativeUrl returned incorrect url"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetSchemeDefaultPortHttpScheme() { + String url = "https://hello.com/level1/level2/level3"; + Uri uri = Uri.create(url); + assertEquals(443, uri.getSchemeDefaultPort(), "schema default port should be 443 for https url"); + + String url2 = "http://hello.com/level1/level2/level3"; + Uri uri2 = Uri.create(url2); + assertEquals(80, uri2.getSchemeDefaultPort(), "schema default port should be 80 for http url"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetSchemeDefaultPortWebSocketScheme() { + String url = "wss://hello.com/level1/level2/level3"; + Uri uri = Uri.create(url); + assertEquals(443, uri.getSchemeDefaultPort(), "schema default port should be 443 for wss url"); + + String url2 = "ws://hello.com/level1/level2/level3"; + Uri uri2 = Uri.create(url2); + assertEquals(80, uri2.getSchemeDefaultPort(), "schema default port should be 80 for ws url"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetExplicitPort() { + String url = "http://hello.com/level1/level2/level3"; + Uri uri = Uri.create(url); + assertEquals(80, uri.getExplicitPort(), "getExplicitPort should return port 80 for http url when port is not specified in url"); + + String url2 = "http://hello.com:8080/level1/level2/level3"; + Uri uri2 = Uri.create(url2); + assertEquals(8080, uri2.getExplicitPort(), "getExplicitPort should return the port given in the url"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testEquals() { + String url = "http://user@hello.com:8080/level1/level2/level3?q=1"; + Uri createdUri = Uri.create(url); + Uri constructedUri = new Uri("http", "user", "hello.com", 8080, "/level1/level2/level3", "q=1", null); + assertEquals(createdUri, constructedUri, "The equals method returned false for two equal urls"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + void testFragment() { + String url = "http://user@hello.com:8080/level1/level2/level3?q=1"; + String fragment = "foo"; + String urlWithFragment = url + '#' + fragment; + Uri uri = Uri.create(urlWithFragment); + assertEquals(uri.getFragment(), fragment, "Fragment should be extracted"); + assertEquals(url, uri.toUrl(), "toUrl should return without fragment"); + assertEquals(urlWithFragment, uri.toFullUrl(), "toFullUrl should return with fragment"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + void testRelativeFragment() { + Uri uri = Uri.create(Uri.create("http://user@hello.com:8080/"), "/level1/level2/level3?q=1#foo"); + assertEquals("foo", uri.getFragment(), "fragment should be kept when computing a relative url"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testIsWebsocket() { + String url = "http://user@hello.com:8080/level1/level2/level3?q=1"; + Uri uri = Uri.create(url); + assertFalse(uri.isWebSocket(), "isWebSocket should return false for http url"); + + url = "https://user@hello.com:8080/level1/level2/level3?q=1"; + uri = Uri.create(url); + assertFalse(uri.isWebSocket(), "isWebSocket should return false for https url"); + + url = "ws://user@hello.com:8080/level1/level2/level3?q=1"; + uri = Uri.create(url); + assertTrue(uri.isWebSocket(), "isWebSocket should return true for ws url"); + + url = "wss://user@hello.com:8080/level1/level2/level3?q=1"; + uri = Uri.create(url); + assertTrue(uri.isWebSocket(), "isWebSocket should return true for wss url"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void creatingUriWithDefinedSchemeAndHostWorks() { + Uri.create("http://localhost/"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void creatingUriWithMissingSchemeThrowsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> Uri.create("localhost")); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void creatingUriWithMissingHostThrowsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> Uri.create("http://")); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetAuthority() { + Uri uri = Uri.create("http://stackoverflow.com/questions/17814461/jacoco-maven-testng-0-test-coverage"); + assertEquals("stackoverflow.com:80", uri.getAuthority(), "Incorrect authority returned from getAuthority"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetAuthorityWithPortInUrl() { + Uri uri = Uri.create("http://stackoverflow.com:8443/questions/17814461/jacoco-maven-testng-0-test-coverage"); + assertEquals("stackoverflow.com:8443", uri.getAuthority(), "Incorrect authority returned from getAuthority"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetBaseUrl() { + Uri uri = Uri.create("http://stackoverflow.com:8443/questions/17814461/jacoco-maven-testng-0-test-coverage"); + assertEquals("http://stackoverflow.com:8443/", uri.getBaseUrl(), "Incorrect base URL returned from getBaseURL"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testIsSameBaseUrlReturnsFalseWhenPortDifferent() { + Uri uri1 = Uri.create("http://stackoverflow.com:8443/questions/17814461/jacoco-maven-testng-0-test-coverage"); + Uri uri2 = Uri.create("http://stackoverflow.com:8442/questions/1057564/pretty-git-branch-graphs"); + assertFalse(uri1.isSameBase(uri2), "Base URLs should be different, but true was returned from isSameBase"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testIsSameBaseUrlReturnsFalseWhenSchemeDifferent() { + Uri uri1 = Uri.create("http://stackoverflow.com:8443/questions/17814461/jacoco-maven-testng-0-test-coverage"); + Uri uri2 = Uri.create("ws://stackoverflow.com:8443/questions/1057564/pretty-git-branch-graphs"); + assertFalse(uri1.isSameBase(uri2), "Base URLs should be different, but true was returned from isSameBase"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testIsSameBaseUrlReturnsFalseWhenHostDifferent() { + Uri uri1 = Uri.create("http://stackoverflow.com:8443/questions/17814461/jacoco-maven-testng-0-test-coverage"); + Uri uri2 = Uri.create("http://example.com:8443/questions/1057564/pretty-git-branch-graphs"); + assertFalse(uri1.isSameBase(uri2), "Base URLs should be different, but true was returned from isSameBase"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testIsSameBaseUrlReturnsTrueWhenOneUriHasDefaultPort() { + Uri uri1 = Uri.create("http://stackoverflow.com/questions/17814461/jacoco-maven-testng-0-test-coverage"); + Uri uri2 = Uri.create("http://stackoverflow.com/questions/1057564/pretty-git-branch-graphs"); + assertTrue(uri1.isSameBase(uri2), "Base URLs should be same, but false was returned from isSameBase"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetPathWhenPathIsNonEmpty() { + Uri uri = Uri.create("http://stackoverflow.com:8443/questions/17814461/jacoco-maven-testng-0-test-coverage"); + assertEquals("/questions/17814461/jacoco-maven-testng-0-test-coverage", uri.getNonEmptyPath(), "Incorrect path returned from getNonEmptyPath"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetPathWhenPathIsEmpty() { + Uri uri = Uri.create("http://stackoverflow.com/"); + assertEquals("/", uri.getNonEmptyPath(), "Incorrect path returned from getNonEmptyPath"); + } +} diff --git a/client/src/test/java/org/asynchttpclient/util/HttpUtilsTest.java b/client/src/test/java/org/asynchttpclient/util/HttpUtilsTest.java new file mode 100644 index 0000000000..57d031498d --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/util/HttpUtilsTest.java @@ -0,0 +1,172 @@ +/* + * 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.util; + +import io.github.artsok.RepeatedIfExceptionsTest; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.asynchttpclient.DefaultAsyncHttpClientConfig; +import org.asynchttpclient.Dsl; +import org.asynchttpclient.Param; +import org.asynchttpclient.Request; +import org.asynchttpclient.uri.Uri; + +import java.net.URLEncoder; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class HttpUtilsTest { + + private static String toUsAsciiString(ByteBuffer buf) { + ByteBuf bb = Unpooled.wrappedBuffer(buf); + try { + return bb.toString(US_ASCII); + } finally { + bb.release(); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testExtractCharsetWithoutQuotes() { + Charset charset = HttpUtils.extractContentTypeCharsetAttribute("text/html; charset=iso-8859-1"); + assertEquals(ISO_8859_1, charset); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testExtractCharsetWithSingleQuotes() { + Charset charset = HttpUtils.extractContentTypeCharsetAttribute("text/html; charset='iso-8859-1'"); + assertEquals(ISO_8859_1, charset); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testExtractCharsetWithDoubleQuotes() { + Charset charset = HttpUtils.extractContentTypeCharsetAttribute("text/html; charset=\"iso-8859-1\""); + assertEquals(ISO_8859_1, charset); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testExtractCharsetWithDoubleQuotesAndSpaces() { + Charset charset = HttpUtils.extractContentTypeCharsetAttribute("text/html; charset= \"iso-8859-1\" "); + assertEquals(ISO_8859_1, charset); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testExtractCharsetFallsBackToUtf8() { + Charset charset = HttpUtils.extractContentTypeCharsetAttribute(APPLICATION_JSON.toString()); + assertNull(charset); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetHostHeader() { + Uri uri = Uri.create("https://stackoverflow.com/questions/1057564/pretty-git-branch-graphs"); + String hostHeader = HttpUtils.hostHeader(uri); + assertEquals("stackoverflow.com", hostHeader, "Incorrect hostHeader returned"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testDefaultFollowRedirect() { + Request request = Dsl.get("https://shieldblaze.com/").setVirtualHost("shieldblaze.com").setFollowRedirect(false).build(); + DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().build(); + boolean followRedirect = HttpUtils.followRedirect(config, request); + assertFalse(followRedirect, "Default value of redirect should be false"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetFollowRedirectInRequest() { + Request request = Dsl.get("https://stackoverflow.com/questions/1057564").setFollowRedirect(true).build(); + DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().build(); + boolean followRedirect = HttpUtils.followRedirect(config, request); + assertTrue(followRedirect, "Follow redirect must be true as set in the request"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetFollowRedirectInConfig() { + Request request = Dsl.get("https://stackoverflow.com/questions/1057564").build(); + DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); + boolean followRedirect = HttpUtils.followRedirect(config, request); + assertTrue(followRedirect, "Follow redirect should be equal to value specified in config when not specified in request"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testGetFollowRedirectPriorityGivenToRequest() { + Request request = Dsl.get("https://stackoverflow.com/questions/1057564").setFollowRedirect(false).build(); + DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); + boolean followRedirect = HttpUtils.followRedirect(config, request); + assertFalse(followRedirect, "Follow redirect value set in request should be given priority"); + } + + private static void formUrlEncoding(Charset charset) throws Exception { + String key = "key"; + String value = "中文"; + List params = new ArrayList<>(); + params.add(new Param(key, value)); + ByteBuffer ahcBytes = HttpUtils.urlEncodeFormParams(params, charset); + String ahcString = toUsAsciiString(ahcBytes); + String jdkString = key + '=' + URLEncoder.encode(value, charset); + assertEquals(ahcString, jdkString); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void formUrlEncodingShouldSupportUtf8Charset() throws Exception { + formUrlEncoding(UTF_8); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void formUrlEncodingShouldSupportNonUtf8Charset() throws Exception { + formUrlEncoding(Charset.forName("GBK")); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void computeOriginForPlainUriWithImplicitPort() { + assertEquals("http://foo.com/", HttpUtils.originHeader(Uri.create("ws://foo.com/bar"))); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void computeOriginForPlainUriWithDefaultPort() { + assertEquals("http://foo.com/", HttpUtils.originHeader(Uri.create("ws://foo.com:80/bar"))); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void computeOriginForPlainUriWithNonDefaultPort() { + assertEquals("http://foo.com:81/", HttpUtils.originHeader(Uri.create("ws://foo.com:81/bar"))); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void computeOriginForSecuredUriWithImplicitPort() { + assertEquals("https://foo.com/", HttpUtils.originHeader(Uri.create("wss://foo.com/bar"))); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void computeOriginForSecuredUriWithDefaultPort() { + assertEquals("https://foo.com/", HttpUtils.originHeader(Uri.create("wss://foo.com:443/bar"))); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void computeOriginForSecuredUriWithNonDefaultPort() { + assertEquals("https://foo.com:444/", HttpUtils.originHeader(Uri.create("wss://foo.com:444/bar"))); + } +} diff --git a/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java b/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java new file mode 100644 index 0000000000..ee3966cf0f --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/util/Utf8UrlEncoderTest.java @@ -0,0 +1,37 @@ +/* + * 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.util; + +import io.github.artsok.RepeatedIfExceptionsTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class Utf8UrlEncoderTest { + + @RepeatedIfExceptionsTest(repeats = 5) + public void testBasics() { + assertEquals("foobar", Utf8UrlEncoder.encodeQueryElement("foobar")); + assertEquals("a%26b", Utf8UrlEncoder.encodeQueryElement("a&b")); + assertEquals("a%2Bb", Utf8UrlEncoder.encodeQueryElement("a+b")); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void testPercentageEncoding() { + assertEquals("foobar", Utf8UrlEncoder.percentEncodeQueryElement("foobar")); + assertEquals("foo%2Abar", Utf8UrlEncoder.percentEncodeQueryElement("foo*bar")); + assertEquals("foo~b_ar", Utf8UrlEncoder.percentEncodeQueryElement("foo~b_ar")); + } +} diff --git a/client/src/test/java/org/asynchttpclient/ws/AbstractBasicWebSocketTest.java b/client/src/test/java/org/asynchttpclient/ws/AbstractBasicWebSocketTest.java new file mode 100644 index 0000000000..4e1ea362de --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ws/AbstractBasicWebSocketTest.java @@ -0,0 +1,66 @@ +/* + * 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.ws; + +import org.asynchttpclient.AbstractBasicTest; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; +import org.junit.jupiter.api.BeforeEach; + +import static org.asynchttpclient.test.TestUtils.addHttpConnector; + +public abstract class AbstractBasicWebSocketTest extends AbstractBasicTest { + + @Override + @BeforeEach + public void setUpGlobal() throws Exception { + server = new Server(); + ServerConnector connector = addHttpConnector(server); + server.setHandler(configureHandler()); + server.start(); + port1 = connector.getLocalPort(); + logger.info("Local HTTP server started successfully"); + } + + @Override + public void tearDownGlobal() throws Exception { + if (server != null) { + server.stop(); + } + } + + @Override + protected String getTargetUrl() { + return String.format("ws://localhost:%d/", port1); + } + + @Override + public AbstractHandler configureHandler() { + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + server.setHandler(context); + + // Configure specific websocket behavior + JettyWebSocketServletContainerInitializer.configure(context, (servletContext, wsContainer) -> { + // Configure default max size + wsContainer.setMaxTextMessageSize(65535); + + // Add websockets + wsContainer.addMapping("/", EchoWebSocket.class); + }); + return context; + } +} diff --git a/client/src/test/java/org/asynchttpclient/ws/ByteMessageTest.java b/client/src/test/java/org/asynchttpclient/ws/ByteMessageTest.java new file mode 100644 index 0000000000..a265376494 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ws/ByteMessageTest.java @@ -0,0 +1,211 @@ +/* + * 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.ws; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.AsyncHttpClient; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +public class ByteMessageTest extends AbstractBasicWebSocketTest { + + private static final byte[] ECHO_BYTES = "ECHO".getBytes(StandardCharsets.UTF_8); + public static final byte[] BYTES = new byte[0]; + + private void echoByte0(boolean enableCompression) throws Exception { + try (AsyncHttpClient c = asyncHttpClient(config().setEnablewebSocketCompression(enableCompression))) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference receivedBytes = new AtomicReference<>(BYTES); + + WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + latch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + + @Override + public void onBinaryFrame(byte[] frame, boolean finalFragment, int rsv) { + receivedBytes.set(frame); + latch.countDown(); + } + }).build()).get(); + + websocket.sendBinaryFrame(ECHO_BYTES); + + latch.await(); + assertArrayEquals(ECHO_BYTES, receivedBytes.get()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void echoByte() throws Exception { + echoByte0(false); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void echoByteCompressed() throws Exception { + echoByte0(true); + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void echoTwoMessagesTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(2); + final AtomicReference text = new AtomicReference<>(null); + + WebSocket websocket = client.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + latch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + + @Override + public void onBinaryFrame(byte[] frame, boolean finalFragment, int rsv) { + if (text.get() == null) { + text.set(frame); + } else { + byte[] n = new byte[text.get().length + frame.length]; + System.arraycopy(text.get(), 0, n, 0, text.get().length); + System.arraycopy(frame, 0, n, text.get().length, frame.length); + text.set(n); + } + latch.countDown(); + } + + }).build()).get(); + + websocket.sendBinaryFrame(ECHO_BYTES); + websocket.sendBinaryFrame(ECHO_BYTES); + + latch.await(); + assertArrayEquals("ECHOECHO".getBytes(), text.get()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void echoOnOpenMessagesTest() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(2); + final AtomicReference text = new AtomicReference<>(null); + + client.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onOpen(WebSocket websocket) { + websocket.sendBinaryFrame(ECHO_BYTES); + websocket.sendBinaryFrame(ECHO_BYTES); + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + latch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + + @Override + public void onBinaryFrame(byte[] frame, boolean finalFragment, int rsv) { + if (text.get() == null) { + text.set(frame); + } else { + byte[] n = new byte[text.get().length + frame.length]; + System.arraycopy(text.get(), 0, n, 0, text.get().length); + System.arraycopy(frame, 0, n, text.get().length, frame.length); + text.set(n); + } + latch.countDown(); + } + + }).build()).get(); + + latch.await(); + assertArrayEquals(text.get(), "ECHOECHO".getBytes()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void echoFragments() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference text = new AtomicReference<>(null); + + WebSocket websocket = client.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + latch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + + @Override + public void onBinaryFrame(byte[] frame, boolean finalFragment, int rsv) { + if (text.get() == null) { + text.set(frame); + } else { + byte[] n = new byte[text.get().length + frame.length]; + System.arraycopy(text.get(), 0, n, 0, text.get().length); + System.arraycopy(frame, 0, n, text.get().length, frame.length); + text.set(n); + } + latch.countDown(); + } + + }).build()).get(); + websocket.sendBinaryFrame(ECHO_BYTES, false, 0); + websocket.sendContinuationFrame(ECHO_BYTES, true, 0); + latch.await(); + assertArrayEquals("ECHOECHO".getBytes(), text.get()); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/ws/CloseCodeReasonMessageTest.java b/client/src/test/java/org/asynchttpclient/ws/CloseCodeReasonMessageTest.java new file mode 100644 index 0000000000..c87dcc2b16 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ws/CloseCodeReasonMessageTest.java @@ -0,0 +1,173 @@ +/* + * 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.ws; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.AsyncHttpClient; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CloseCodeReasonMessageTest extends AbstractBasicWebSocketTest { + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void onCloseWithCode() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference text = new AtomicReference<>(""); + + WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new Listener(latch, text)).build()).get(); + + websocket.sendCloseFrame(); + + latch.await(); + assertTrue(text.get().startsWith("1000"), "Expected a 1000 code but got " + text.get()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void onCloseWithCodeServerClose() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference text = new AtomicReference<>(""); + + c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new Listener(latch, text)).build()).get(); + + latch.await(); + assertEquals("1001-Connection Idle Timeout", text.get()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void getWebSocketThrowsException() throws Throwable { + final CountDownLatch latch = new CountDownLatch(1); + try (AsyncHttpClient client = asyncHttpClient()) { + assertThrows(Exception.class, () -> { + client.prepareGet("http://apache.org/").execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + } + + @Override + public void onError(Throwable t) { + latch.countDown(); + } + }).build()).get(); + }); + } + + latch.await(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void wrongStatusCode() throws Exception { + try (AsyncHttpClient client = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference throwable = new AtomicReference<>(); + + client.prepareGet("ws://apache.org").execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + } + + @Override + public void onError(Throwable t) { + throwable.set(t); + latch.countDown(); + } + }).build()); + + latch.await(); + assertInstanceOf(Exception.class, throwable.get()); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void wrongProtocolCode() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference throwable = new AtomicReference<>(); + + c.prepareGet("ws://www.google.com").execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + } + + @Override + public void onError(Throwable t) { + throwable.set(t); + latch.countDown(); + } + }).build()); + + latch.await(); + assertInstanceOf(IOException.class, throwable.get()); + } + } + + public static final class Listener implements WebSocketListener { + + final CountDownLatch latch; + final AtomicReference text; + + Listener(CountDownLatch latch, AtomicReference text) { + this.latch = latch; + this.text = text; + } + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + text.set(code + "-" + reason); + latch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/ws/EchoWebSocket.java b/client/src/test/java/org/asynchttpclient/ws/EchoWebSocket.java new file mode 100644 index 0000000000..0161564fc1 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ws/EchoWebSocket.java @@ -0,0 +1,76 @@ +/* + * 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.ws; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.time.Duration; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class EchoWebSocket extends WebSocketAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(EchoWebSocket.class); + + @Override + public void onWebSocketConnect(Session sess) { + super.onWebSocketConnect(sess); + sess.setIdleTimeout(Duration.ofMillis(10_000)); + } + + @Override + public void onWebSocketClose(int statusCode, String reason) { + getSession().close(); + super.onWebSocketClose(statusCode, reason); + } + + @Override + public void onWebSocketBinary(byte[] payload, int offset, int len) { + if (isNotConnected()) { + return; + } + try { + LOGGER.debug("Received binary frame of size {}: {}", len, new String(payload, offset, len, UTF_8)); + getRemote().sendBytes(ByteBuffer.wrap(payload, offset, len)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onWebSocketText(String message) { + if (isNotConnected()) { + return; + } + + if ("CLOSE".equals(message)) { + getSession().close(); + return; + } + + try { + LOGGER.debug("Received text frame of size: {}", message); + getRemote().sendString(message); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/ws/ProxyTunnellingTest.java b/client/src/test/java/org/asynchttpclient/ws/ProxyTunnellingTest.java new file mode 100644 index 0000000000..ce9cda3dc4 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ws/ProxyTunnellingTest.java @@ -0,0 +1,154 @@ +/* + * 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. + * 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.ws; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.proxy.ProxyServer; +import org.eclipse.jetty.proxy.ConnectHandler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.Dsl.proxyServer; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.asynchttpclient.test.TestUtils.addHttpsConnector; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Proxy usage tests. + */ +public class ProxyTunnellingTest extends AbstractBasicWebSocketTest { + + private Server server2; + + @Override + @BeforeAll + public void setUpGlobal() throws Exception { + // Don't call Global + } + + @Override + @AfterAll + public void tearDownGlobal() throws Exception { + server.stop(); + server2.stop(); + } + + @AfterEach + public void cleanup() throws Exception { + super.tearDownGlobal(); + server2.stop(); + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void echoWSText() throws Exception { + runTest(false); + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void echoWSSText() throws Exception { + runTest(true); + } + + private void runTest(boolean secure) throws Exception { + setUpServers(secure); + String targetUrl = String.format("%s://localhost:%d/", secure ? "wss" : "ws", port2); + + // CONNECT happens over HTTP, not HTTPS + ProxyServer ps = proxyServer("localhost", port1).build(); + try (AsyncHttpClient asyncHttpClient = asyncHttpClient(config().setProxyServer(ps).setUseInsecureTrustManager(true))) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference text = new AtomicReference<>(""); + + WebSocket websocket = asyncHttpClient.prepareGet(targetUrl).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onTextFrame(String payload, boolean finalFragment, int rsv) { + text.set(payload); + latch.countDown(); + } + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + latch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + }).build()).get(); + + websocket.sendTextFrame("ECHO"); + + latch.await(); + assertEquals("ECHO", text.get()); + } + } + + private void setUpServers(boolean targetHttps) throws Exception { + server = new Server(); + ServerConnector connector = addHttpConnector(server); + server.setHandler(new ConnectHandler()); + server.start(); + port1 = connector.getLocalPort(); + + server2 = new Server(); + ServerConnector connector2 = targetHttps ? addHttpsConnector(server2) : addHttpConnector(server2); + server2.setHandler(configureHandler()); + server2.start(); + port2 = connector2.getLocalPort(); + + logger.info("Local HTTP server started successfully"); + } + + @Override + public AbstractHandler configureHandler() { + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + server2.setHandler(context); + + // Configure specific websocket behavior + JettyWebSocketServletContainerInitializer.configure(context, (servletContext, wsContainer) -> { + // Configure default max size + wsContainer.setMaxTextMessageSize(65535); + + // Add websockets + wsContainer.addMapping("/", EchoWebSocket.class); + }); + return context; + } +} diff --git a/client/src/test/java/org/asynchttpclient/ws/RedirectTest.java b/client/src/test/java/org/asynchttpclient/ws/RedirectTest.java new file mode 100644 index 0000000000..c0581d9a00 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ws/RedirectTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 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.ws; + +import io.github.artsok.RepeatedIfExceptionsTest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.asynchttpclient.AsyncHttpClient; +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; +import org.eclipse.jetty.server.handler.HandlerList; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.asynchttpclient.Dsl.config; +import static org.asynchttpclient.test.TestUtils.addHttpConnector; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class RedirectTest extends AbstractBasicWebSocketTest { + + @BeforeEach + public void setUpGlobals() throws Exception { + server = new Server(); + ServerConnector connector1 = addHttpConnector(server); + ServerConnector connector2 = addHttpConnector(server); + + HandlerList list = new HandlerList(); + list.addHandler(new AbstractHandler() { + @Override + public void handle(String s, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException { + if (request.getLocalPort() == port2) { + httpServletResponse.sendRedirect(getTargetUrl()); + } + } + }); + list.addHandler(configureHandler()); + server.setHandler(list); + + server.start(); + port1 = connector1.getLocalPort(); + port2 = connector2.getLocalPort(); + logger.info("Local HTTP server started successfully"); + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void testRedirectToWSResource() throws Exception { + try (AsyncHttpClient c = asyncHttpClient(config().setFollowRedirect(true))) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference text = new AtomicReference<>(""); + + WebSocket websocket = c.prepareGet(getRedirectURL()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onOpen(WebSocket websocket) { + text.set("OnOpen"); + latch.countDown(); + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + }).build()).get(); + + latch.await(); + assertEquals("OnOpen", text.get()); + websocket.sendCloseFrame(); + } + } + + private String getRedirectURL() { + return String.format("ws://localhost:%d/", port2); + } +} diff --git a/client/src/test/java/org/asynchttpclient/ws/TextMessageTest.java b/client/src/test/java/org/asynchttpclient/ws/TextMessageTest.java new file mode 100644 index 0000000000..f25e0e5333 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ws/TextMessageTest.java @@ -0,0 +1,368 @@ +/* + * 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.ws; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.AsyncHttpClient; +import org.junit.jupiter.api.Timeout; + +import java.net.ConnectException; +import java.net.UnknownHostException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +public class TextMessageTest extends AbstractBasicWebSocketTest { + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void onOpen() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference text = new AtomicReference<>(""); + + c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onOpen(WebSocket websocket) { + text.set("OnOpen"); + latch.countDown(); + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + }).build()).get(); + + latch.await(); + assertEquals(text.get(), "OnOpen"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void onEmptyListenerTest() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + WebSocket websocket = null; + try { + websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().build()).get(); + } catch (Throwable t) { + fail(); + } + assertNotNull(websocket); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void onFailureTest() throws Throwable { + try (AsyncHttpClient c = asyncHttpClient()) { + c.prepareGet("ws://abcdefg").execute(new WebSocketUpgradeHandler.Builder().build()).get(); + } catch (ExecutionException e) { + if (!(e.getCause() instanceof UnknownHostException || e.getCause() instanceof ConnectException)) { + fail("Exception is not UnknownHostException or ConnectException but rather: " + e); + } + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void onTimeoutCloseTest() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference text = new AtomicReference<>(""); + + c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + text.set("OnClose"); + latch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + }).build()).get(); + + latch.await(); + assertEquals(text.get(), "OnClose"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void onClose() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference text = new AtomicReference<>(""); + + WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + text.set("OnClose"); + latch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + }).build()).get(); + + websocket.sendCloseFrame(); + + latch.await(); + assertEquals(text.get(), "OnClose"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void echoText() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference text = new AtomicReference<>(""); + + WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onTextFrame(String payload, boolean finalFragment, int rsv) { + text.set(payload); + latch.countDown(); + } + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + latch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + }).build()).get(); + + websocket.sendTextFrame("ECHO"); + + latch.await(); + assertEquals(text.get(), "ECHO"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void echoDoubleListenerText() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(2); + final AtomicReference text = new AtomicReference<>(""); + + WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onTextFrame(String payload, boolean finalFragment, int rsv) { + text.set(payload); + latch.countDown(); + } + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + latch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + }).addWebSocketListener(new WebSocketListener() { + + @Override + public void onTextFrame(String payload, boolean finalFragment, int rsv) { + text.set(text.get() + payload); + latch.countDown(); + } + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + latch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + }).build()).get(); + + websocket.sendTextFrame("ECHO"); + + latch.await(); + assertEquals(text.get(), "ECHOECHO"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void echoTwoMessagesTest() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(2); + final AtomicReference text = new AtomicReference<>(""); + + c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onTextFrame(String payload, boolean finalFragment, int rsv) { + text.set(text.get() + payload); + latch.countDown(); + } + + @Override + public void onOpen(WebSocket websocket) { + websocket.sendTextFrame("ECHO"); + websocket.sendTextFrame("ECHO"); + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + latch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + }).build()).get(); + + latch.await(); + assertEquals(text.get(), "ECHOECHO"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void echoFragments() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference text = new AtomicReference<>(""); + + WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onTextFrame(String payload, boolean finalFragment, int rsv) { + text.set(payload); + latch.countDown(); + } + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + latch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + latch.countDown(); + } + }).build()).get(); + + websocket.sendTextFrame("ECHO", false, 0); + websocket.sendContinuationFrame("ECHO", true, 0); + + latch.await(); + assertEquals(text.get(), "ECHOECHO"); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void echoTextAndThenClose() throws Throwable { + try (AsyncHttpClient c = asyncHttpClient()) { + final CountDownLatch textLatch = new CountDownLatch(1); + final CountDownLatch closeLatch = new CountDownLatch(1); + final AtomicReference text = new AtomicReference<>(""); + + final WebSocket websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onTextFrame(String payload, boolean finalFragment, int rsv) { + text.set(text.get() + payload); + textLatch.countDown(); + } + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + closeLatch.countDown(); + } + + @Override + public void onError(Throwable t) { + t.printStackTrace(); + closeLatch.countDown(); + } + }).build()).get(); + + websocket.sendTextFrame("ECHO"); + textLatch.await(); + + websocket.sendTextFrame("CLOSE"); + closeLatch.await(); + + assertEquals(text.get(), "ECHO"); + } + } +} diff --git a/client/src/test/java/org/asynchttpclient/ws/WebSocketWriteFutureTest.java b/client/src/test/java/org/asynchttpclient/ws/WebSocketWriteFutureTest.java new file mode 100644 index 0000000000..e0edc54998 --- /dev/null +++ b/client/src/test/java/org/asynchttpclient/ws/WebSocketWriteFutureTest.java @@ -0,0 +1,170 @@ +/* + * 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.ws; + +import io.github.artsok.RepeatedIfExceptionsTest; +import org.asynchttpclient.AsyncHttpClient; +import org.junit.jupiter.api.Timeout; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.asynchttpclient.Dsl.asyncHttpClient; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class WebSocketWriteFutureTest extends AbstractBasicWebSocketTest { + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void sendTextMessage() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + getWebSocket(c).sendTextFrame("TEXT").get(10, TimeUnit.SECONDS); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void sendTextMessageExpectFailure() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + CountDownLatch closeLatch = new CountDownLatch(1); + WebSocket websocket = getWebSocket(c, closeLatch); + websocket.sendCloseFrame(); + closeLatch.await(1, TimeUnit.SECONDS); + assertThrows(Exception.class, () -> websocket.sendTextFrame("TEXT").get(10, TimeUnit.SECONDS)); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void sendByteMessage() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + getWebSocket(c).sendBinaryFrame("BYTES".getBytes()).get(10, TimeUnit.SECONDS); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void sendByteMessageExpectFailure() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + CountDownLatch closeLatch = new CountDownLatch(1); + WebSocket websocket = getWebSocket(c, closeLatch); + websocket.sendCloseFrame(); + closeLatch.await(1, TimeUnit.SECONDS); + assertThrows(Exception.class, () -> websocket.sendBinaryFrame("BYTES".getBytes()).get(10, TimeUnit.SECONDS)); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void sendPingMessage() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + getWebSocket(c).sendPingFrame("PING".getBytes()).get(10, TimeUnit.SECONDS); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void sendPingMessageExpectFailure() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + CountDownLatch closeLatch = new CountDownLatch(1); + WebSocket websocket = getWebSocket(c, closeLatch); + websocket.sendCloseFrame(); + closeLatch.await(1, TimeUnit.SECONDS); + assertThrows(Exception.class, () -> websocket.sendPingFrame("PING".getBytes()).get(10, TimeUnit.SECONDS)); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void sendPongMessage() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + getWebSocket(c).sendPongFrame("PONG".getBytes()).get(10, TimeUnit.SECONDS); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void sendPongMessageExpectFailure() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + CountDownLatch closeLatch = new CountDownLatch(1); + WebSocket websocket = getWebSocket(c, closeLatch); + websocket.sendCloseFrame(); + closeLatch.await(1, TimeUnit.SECONDS); + assertThrows(Exception.class, () -> websocket.sendPongFrame("PONG".getBytes()).get(1, TimeUnit.SECONDS)); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void streamBytes() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + getWebSocket(c).sendBinaryFrame("STREAM".getBytes(), true, 0).get(1, TimeUnit.SECONDS); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + @Timeout(unit = TimeUnit.MILLISECONDS, value = 60000) + public void streamBytesExpectFailure() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + CountDownLatch closeLatch = new CountDownLatch(1); + WebSocket websocket = getWebSocket(c, closeLatch); + websocket.sendCloseFrame(); + closeLatch.await(1, TimeUnit.SECONDS); + assertThrows(Exception.class, () -> websocket.sendBinaryFrame("STREAM".getBytes(), true, 0).get(1, TimeUnit.SECONDS)); + } + } + + @RepeatedIfExceptionsTest(repeats = 5) + public void streamText() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + getWebSocket(c).sendTextFrame("STREAM", true, 0).get(1, TimeUnit.SECONDS); + } + } + + + @RepeatedIfExceptionsTest(repeats = 5) + public void streamTextExpectFailure() throws Exception { + try (AsyncHttpClient c = asyncHttpClient()) { + CountDownLatch closeLatch = new CountDownLatch(1); + WebSocket websocket = getWebSocket(c, closeLatch); + websocket.sendCloseFrame(); + closeLatch.await(1, TimeUnit.SECONDS); + assertThrows(Exception.class, () -> websocket.sendTextFrame("STREAM", true, 0).get(1, TimeUnit.SECONDS)); + } + } + + private WebSocket getWebSocket(final AsyncHttpClient c) throws Exception { + return c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().build()).get(); + } + + private WebSocket getWebSocket(final AsyncHttpClient c, CountDownLatch closeLatch) throws Exception { + return c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() { + + @Override + public void onOpen(WebSocket websocket) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onClose(WebSocket websocket, int code, String reason) { + closeLatch.countDown(); + } + }).build()).get(); + } +} diff --git a/api/src/test/resources/300k.png b/client/src/test/resources/300k.png similarity index 100% rename from api/src/test/resources/300k.png rename to client/src/test/resources/300k.png diff --git a/client/src/test/resources/META-INF/services/org.apache.juli.logging.Log b/client/src/test/resources/META-INF/services/org.apache.juli.logging.Log new file mode 100644 index 0000000000..9099aa34ec --- /dev/null +++ b/client/src/test/resources/META-INF/services/org.apache.juli.logging.Log @@ -0,0 +1 @@ +org.asynchttpclient.test.Slf4jJuliLog \ No newline at end of file diff --git a/api/src/test/resources/SimpleTextFile.txt b/client/src/test/resources/SimpleTextFile.txt similarity index 100% rename from api/src/test/resources/SimpleTextFile.txt rename to client/src/test/resources/SimpleTextFile.txt diff --git a/api/src/test/resources/client.keystore b/client/src/test/resources/client.keystore similarity index 100% rename from api/src/test/resources/client.keystore rename to client/src/test/resources/client.keystore diff --git a/client/src/test/resources/empty.txt b/client/src/test/resources/empty.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/src/test/resources/gzip.txt.gz b/client/src/test/resources/gzip.txt.gz similarity index 100% rename from api/src/test/resources/gzip.txt.gz rename to client/src/test/resources/gzip.txt.gz diff --git a/client/src/test/resources/kerberos.jaas b/client/src/test/resources/kerberos.jaas new file mode 100644 index 0000000000..cd5b316bf1 --- /dev/null +++ b/client/src/test/resources/kerberos.jaas @@ -0,0 +1,8 @@ + +alice { + com.sun.security.auth.module.Krb5LoginModule required refreshKrb5Config=true useKeyTab=false principal="alice"; +}; + +bob { + com.sun.security.auth.module.Krb5LoginModule required refreshKrb5Config=true useKeyTab=false storeKey=true principal="bob/service.ws.apache.org"; +}; diff --git a/client/src/test/resources/logback-test.xml b/client/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..4b6a087912 --- /dev/null +++ b/client/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d [%thread] %level %logger - %m%n + + + + + + + + + + diff --git a/api/src/test/resources/realm.properties b/client/src/test/resources/realm.properties similarity index 100% rename from api/src/test/resources/realm.properties rename to client/src/test/resources/realm.properties diff --git a/api/src/test/resources/ssltest-cacerts.jks b/client/src/test/resources/ssltest-cacerts.jks similarity index 100% rename from api/src/test/resources/ssltest-cacerts.jks rename to client/src/test/resources/ssltest-cacerts.jks diff --git a/api/src/test/resources/ssltest-keystore.jks b/client/src/test/resources/ssltest-keystore.jks similarity index 100% rename from api/src/test/resources/ssltest-keystore.jks rename to client/src/test/resources/ssltest-keystore.jks diff --git a/client/src/test/resources/test_sample_message.eml b/client/src/test/resources/test_sample_message.eml new file mode 100644 index 0000000000..79ecb11a50 --- /dev/null +++ b/client/src/test/resources/test_sample_message.eml @@ -0,0 +1,171 @@ +Return-Path: <${OX_USER_EMAIL1}> +To: ${OX_USER_FIRST_NAME} ${OX_USER_LAST_NAME} <${OX_USER_EMAIL1}> +Subject: Testing ${OX_USER_FIRST_NAME} ${OX_USER_LAST_NAME}' MIME E-mail composing and sending PHP class: HTML message +From: ${username} <${OX_USER_EMAIL1}> +Reply-To: ${username} <${OX_USER_EMAIL1}> +Sender: ${OX_USER_EMAIL1} +X-Mailer: http://www.phpclasses.org/mimemessage $Revision: 1.63 $ (mail) +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="652b8c4dcb00cdcdda1e16af36781caf" +Message-ID: <20050430192829.0489.mlemos@acm.org> +Date: Sat, 30 Apr 2005 19:28:29 -0300 + + +--652b8c4dcb00cdcdda1e16af36781caf +Content-Type: multipart/related; boundary="6a82fb459dcaacd40ab3404529e808dc" + + +--6a82fb459dcaacd40ab3404529e808dc +Content-Type: multipart/alternative; boundary="69c1683a3ee16ef7cf16edd700694a2f" + + +--69c1683a3ee16ef7cf16edd700694a2f +Content-Type: text/plain; charset=ISO-8859-1 +Content-Transfer-Encoding: quoted-printable + +This is an HTML message. Please use an HTML capable mail program to read +this message. + +--69c1683a3ee16ef7cf16edd700694a2f +Content-Type: text/html; charset=ISO-8859-1 +Content-Transfer-Encoding: quoted-printable + + + +Testing Manuel Lemos' MIME E-mail composing and sending PHP class: H= +TML message + + + + + + + +
+

Testing Manuel Lemos' MIME E-mail composing and sending PHP cla= +ss: HTML message

+
+

Hello Manuel,

+This message is just to let you know that the MIME E-mail message composing and sending PHP class is working as expected.

+

Here is an image embedded in a message as a separate part:

= +
+
Than= +k you,
+mlemos

+
+ + +--69c1683a3ee16ef7cf16edd700694a2f-- + +--6a82fb459dcaacd40ab3404529e808dc +Content-Type: image/gif; name="logo.gif" +Content-Transfer-Encoding: base64 +Content-Disposition: inline; filename="logo.gif" +Content-ID: + +R0lGODlhlgAjAPMJAAAAAAAA/y8vLz8/P19fX19f339/f4+Pj4+Pz7+/v/////////////////// +/////yH5BAEAAAkALAAAAACWACMAQwT+MMlJq7046827/2AoHYChGAChAkBylgKgKClFyEl6xDMg +qLFBj3C5uXKplVAxIOxkA8BhdFCpDlMK1urMTrZWbAV8tVS5YsxtxmZHBVOSCcW9zaXyNhslVcto +RBp5NQYxLAYGLi8oSwoJBlE+BiSNj5E/PDQsmy4pAJWQLAKJY5+hXhZ2dDYldFWtNSFPiXssXnZR +k5+1pjpBiDMJUXG/Jo7DI4eKfMSmxsJ9GAUB1NXW19jZ2tvc3d7f4OHi2AgZN5vom1kk6F7s6u/p +m3Ab7AOIiCxOyZuBIv8AOeTJIaYQjiR/kKTr5GQNE3pYSjCJ9mUXClRUsLxaZGciC0X+OlpoOuQo +ZKdNJnIoKfnxRUQh6FLG0iLxIoYnJd0JEKISJyAQDodp3EUDC48oDnUY7HFI3wEDRjzycQJVZCQT +Ol7NK+G0qgtkAcOKHUu2rNmzYTVqRMt2bB49bHompSchqg6HcGeANSMxr8sEa2y2HexnSEUTuWri +SSbkYh7BgGVAnhB1b2REibESYaRoBgqIMYx59tFM9AvQffVG49P5NMZkMlHKhJPJb0knmSKZ6kSX +JtbeF3Am7ocok6c7cM7pU5xcXiJJETUz16qPrzEfaFgZpvzn7h86YV5r/1mxXeAUMVyEIpnVUGpN +RlG2ka9b3lP3pm2l6u7P+l/YLj3+RlEHbz1C0kRxSITQaAcilVBMEzmkkEQO8oSOBNg9SN+AX6hV +z1pjgJiAhwCRsY8ZIp6xj1ruqCgeGeKNGEZwLnIwzTg45qjjjjz2GEA5hAUp5JBEFmnkkSCoWEcZ +X8yohZNK1pFGPQS4hx0qNSLJlk9wCQORYu5QiMd7bUzGVyNlRiOHSlpuKdGEItHQ3HZ18beRRyws +YSY/waDTiHf/tWlWUBAJiMJ1/Z0XXU7N0FnREpKM4NChCgbyRDq9XYpOplaKopN9NMkDnBbG+UMC +QwLWIeaiglES6AjGARcPHCWoVAiatcTnGTABZoLPaPG1phccPv366mEvWEFSLnj+2QaonECwcJt/ +e1Zw3lJvVMmftBdVNQS3UngLCA85YHIQOy6JO9N4eZW7KJwtOUZmGwOMWqejwVW6RQzaikRHX3yI +osKhDAq8wmnKSmdMwNidSOof9ZG2DoV0RfTVmLFtGmNk+CoZna0HQnPHS3AhRbIeDpqmR09E0bsu +soeaw994z+rwQVInvqLenBftYjLOVphLFHhV9qsnez8AEUbQRgO737AxChjmyANxuEFHSGi7hFCV +4jxLst2N8sRJYU+SHiAKjlmCgz2IffbLI5aaQR71hnkxq1ZfHSfKata6YDCJDMAQwY7wOgzhjxgj +VFQnKB5uX4mr9qJ79pann+VcfcSzsSCd2mw5scqRRvlQ6TgcUelYhu75iPE4JejrsJOFQAG01277 +7bjnrvvuvPfu++/ABy887hfc6OPxyCevPDdAVoDA89BHL/301Fdv/fXYZ6/99tx3Pz0FEQAAOw== + +--6a82fb459dcaacd40ab3404529e808dc +Content-Type: image/gif; name="background.gif" +Content-Transfer-Encoding: base64 +Content-Disposition: inline; filename="background.gif" +Content-ID: <4c837ed463ad29c820668e835a270e8a.gif> + +R0lGODlh+wHCAPMAAKPFzKLEy6HDyqHCyaDByJ/Ax56/xp2+xZ28xJy7w5u6wpq5wZm4wJm3v5i2 +vpe1vSwAAAAA+wHCAEME/hDISau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqP +yKRyyWw6n9CodEqtWq+gwSHReHgfjobY8X00FIc019tIHAYS7dqcQCDm3vC4fD4QAhUBBFsMZF8O +hnkLCAYFW11tb1iTlJWWOXJdZZtmC24Eg3hgYntfbXainJ2fgBSZbG5wFAG0E6+RoAZ3CbwJCgya +p3cMbAyevQcFAgMGCcRmxr1uyszOxQq+wF4MdcPFx7zJApfk5eYhr3SSGemRsu3dc+4iAqELhZwO +0X6hkHUHCBRoGtUg0RkEAAUeKhhGAcICBQIODIPooIEBzCTmKcjGYSNd/go3VvQo65zJkyhTqlzJ +sqXLlzBjypxJs6bNmzhz6tzJs6fPn0CDCh1KtKjRo0iTKl3KtKnTp1CXBhhAwECaq1gPNCIwANDU +qmkMcG311apWULmyZt3alcPXAma1FgAlgCxVq2LbRt3LF0Y7hwWoEjLEDZUmff8AOjMkTB5gwYu3 +JbhIQUDEZw+4+aE1aNc0R2vcDYjoDBgpBoUDj95yzzRqbH7qgW4t5vUnAfVAoj7NwOOf1QloN7Ad +u1Xf41b+IlCNsa6rR7DWwTPccTnG5sYvCEKwgPGiZI64A9OsK/Q/BM/0YfuFz13VOwsULLhHps+f +98Hl0zeDRk0X9Qih/vLPWPjFN197aPyB3IJVBLDMdc5t4OB1A0QowYQQ0vIgdilgyGEgG1roYV0j +GufhhyBSWGF2s2yIYosqWsjgjDTWaOONOOao44489ujjj0AGKeSQRBZp5JFIJqnkkkw26eSTUMJU +llpYseXVXWGNdSGWZ6EVF5VWukUVXFdtRUCEU+bFYpRslqNcYKHgk1k8hxWWxjCM0VkdnINJRtkE +lqH3hWZ/CKJYOBBBJxppu/FWh2qzNUrcmQRE6lpvt+UWUKPD9cbIb5bWhmlxbbL5JoUywiMddHRQ +x591GWqwXXdsfJeoeMO5UZ4/AaaHKXv1xVKgfghuNuyB9fUHHYAA/u2CEIHlGbiffWuWyuSJMmKA +bXbbbtuhi9kCUOIEJY57oYsraoduuOfGWO2J6Vor77z01mvvvfjmq+++/Pbr778AByzwwAQXbPDB +CCfcZDobldLRVfLEEgerjQ1EEEemJMiioZEdkggYizSiqMQKl5wCw6qswg+rDTvc6h0Wq9KAJ5tV +oGpJF9YysXn8lCfNL8HE88xw4EyzTDNDR4MMNUhfk40mhXkDTdHimHzjzRpgDcB0MEeHswf1sCZn +GfrQDMrIAYZEkEEOJTQRQweBp5FIDTGCEUiHYWwRXHOPMpLdVgcu+OCEF2744YgnrvjijDfu+OOQ +Ry755JRXbvnl/phnrvnmnHfu+eegZ57RAqSUzptv75E+M+Bb66L6InZwZ7rpr31aLQBhb2pap548 +e7TsIX8dOr/pIIZQQphFHfGqEbtq/J2/DDrZ13Ga0jt8h/XX9TxvfRmmuPVUatb34INCplxakjtm +XOQ7aP74c+k1fE4MD7fefvxBbLEeLldsyq/4o9ZzHOOHylBFS7f4RJxQMx/8MeB4ggIDA02ziLno +wlfGoOByKnUAhZQNWfkzwAXzMEExVFB+86NJ/TDVC4SIZRzFs5Ni5OQ/p7XwLOOwQDXSswgFiYuD +Z4GMP8AjtvGgJk9aYU2davdCeyzRU2LpBwkb2KjvWCU4T/TN/u1S+BKtYUBrXFue8DYQKFoVAzXa +eJh/XiYPpZEOFhAMTnzkk8aQWQU+c7yHJkIGkGd4SkDhMJ9i5qMAOu4RAWfiYk1yxwvfaYCRA8oh +JF14x0bGhgSyaZY07JCMRDLyWWnxTOyc1UmweMaSL5zSKf/xQgnk5lA3TCWWVunCRCrylrjMpS53 +ycte+vKXwAymMIdJzGIa85jITKYyl8nMZjrzmdCMpjSnSc1qWvOa2MymvkY3u9IxMReyW92fuLm6 +2Kmum53SIgZyxx7e9C423AyeNnkUw8RsSnqumsfWKKYnCdozen6iHiGsF483gkF7PIND96oUP7KE +73zteyj8/tK3JfGVqaHkkmhYMDrPJqzwfjRUlij4hzE4ds1pdGSMxgYYjAQZEBRtSeDKSmMMEGYG +ghjU4+osGEF9ZNCEG3SEB2s6LTSIsKcl3CkKO2qEj24Sh/ucw/NmmCdXQQMbsbSlzZoGMkSSBYh5 +kWIkEhWc3aARiVc0qE+hSCklkvCbUpQgFTWYRCy+la1bZGoQvHgBMPIznyT7QBkNgsY05m+NNSQa +Lwx6ijvJsZB69IIdB5nHOjKij9twCCAVGJ7HGlKyiMyhXo0wyUtmoLS2LK0ID+XIEWRys5ycyzg+ +yQ9TtjB2lpyLbZ8qy91mVZK+ReWZVCkNVmp1tMhNrnKX/svc5jr3udCNrnSnS93qWve62M2udrfL +3e5697vgDa94x0ve8pr3vOhNr3rXy972uve98I2vfOdLXxrBS0Uv8lZGUaUh/OKXXRmAV7jMVV+X +QLK4vD0TaoHLWq1UEsEJFu0FXknLh3iyM5EssEtQlrK98ZN5QbNqyl71pwqEza752MfZEqrhljg1 +pYMKkBh3FuKTXtUX+LupMkwcETNCA40D6QNiA3tfdunXAkdOEX+1Ba68tjiqLbVOnKp60oNAam6J +fcyUvTYLAnDHOw8Jjx7Js71YTKWzxX1IV76iyayuWTCwDSIgKJxmqLI5zmp6sg5ZNdV7bkPGQWYh +0EzR/s8+A1THEt6hIrx6IbByRawKHKjfpEfExVREpUEdzKX3dJe5UaQ6UdT0p18VGCfPF2X8S4QD +QgaamI24hi1TtTxZyuVZ6AzK6gBnIbE66DmhImlzxAYouUq0XQ+oUhG039P+rAZgG7u1erYFyy6W +Tt85ddkmHak3PWVaWuePAC9F4Mh6dgdjB/A8tCqbscUxWLmumxp8jsa5A5RuY7xbwtHGtT+Phz69 +nGo0WC60DPt9u0AljxWG8kylh9hsRKw1jbiwx24cDsUKSRwYFPdIq2347NoWkSEAKnG++brnGes7 +sYH1QPVqVdDsOZZXUlN2WYO1soCA9JBoScjNQdvs/n3fKXaxYefOH9BDfD+Z5Db78Dv+WuWUd4Bj +YwPDx1bNiI03BoO7yRi9CzJBBLlQdj5tTbKIOFQqikHjruN6Bovlw5GnXZxjtMXbZ01O2NnhdawL +ASOFw8BIxpOSuutUYWfmBjW0U1S+gczhqy0Wzuhmd7Ur5RYW/01Tz3dKcpYVl/Isrs2jBSyZJ4H7 +LIq+4VYUL2NZaCMgQiY1LXSjFH09wWexvovGvvawX2q+d8/73vv+98APvvCHT/ziG//4yE++8pfP +/OY7//nQj770p0/96lv/+tjPvva3z/3ue//74A+/+MdP/vKb//zoT7/6e3Lf/3KryTDKUPvdBQIB +/q+JwOuPwYEhbFzcYDjDuPN/lARL/FdLRlcZwdUNnTRbGAZt+fcCHCYzGqd0NJZtrsYJFjFGJ2ZQ +m1A2kcZiD+gXLKNsMMZsTQdiFvg/IJUID7RjldFjhAVkGaM/6lASRfYu8KcuS6aDO4hkOfh7p7Jl +bBRlVxYSWSZlfVKDXfZltRJmADFmulJmb3BmBJhbb9YZp1RLV9hmwtUWdBZhnYeFCaZ7Rxdv/5Q8 +gKaCvNBrQ0hCZxhjLhgHXEV1PiQIjhBEkDZT6VFSmkFWhbBppMZBljZqVtZpIUGIqCNqevMYlhdf +qEYKslZ10zZibbgQDkN1IndyTkcLxiFTulZI/muYRsrjbKA4bNYwNR1nPsn2K6J4PKdYbKXYbSM3 +bSQVeWdybWwIa9Rmi0b3FwUEKAcUU+MGTr4AivP2hGSgbqDIbjDobssIb1IlbzSEbslob894gGUY +jYkxeyf3GABnhAK3jeTDYxE0J5uRcEtjdYUnaoMXHStGGxlnNxs4cYgARRt3Y8UobB5XVhhXjyTR +e0jnbfoURkGzDh+wcquACmqFUDD3iiw0LZFmczhmWTknkZ9FdK5IDH0GdArWGaB4kUXHewEpbSZH +kLX2AVA3dVPHamgjNQ8XZG0Ddl2XLF9HOmF3RPmTKGV3IGdXdWl3k2zXiPBVd3nXV3PHOkRpgk5A +lYlgg2F8Fw3WlnZW9HiCB2Q0Y3ic8k2Kl5V4JQhUiXgWFgqUh1e9h3mcpy2epxdm+XnjQ1EiMHoQ +pVtogiWuV3urBxGod4Xnw41huJfjKHvtg3t8GYKEWZiGeZiImZiKuZiM2ZiO+ZiQGZmSOZmUWZmW +eZmYmZmauZmc2ZlCEQEAOw== + +--6a82fb459dcaacd40ab3404529e808dc-- + +--652b8c4dcb00cdcdda1e16af36781caf +Content-Type: text/plain; name="attachment.txt" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="attachment.txt" + +VGhpcyBpcyBqdXN0IGEgcGxhaW4gdGV4dCBhdHRhY2htZW50IGZpbGUgbmFtZWQgYXR0YWNobWVu +dC50eHQgLg== + +--652b8c4dcb00cdcdda1e16af36781caf-- + diff --git a/api/src/test/resources/textfile.txt b/client/src/test/resources/textfile.txt similarity index 100% rename from api/src/test/resources/textfile.txt rename to client/src/test/resources/textfile.txt diff --git a/api/src/test/resources/textfile2.txt b/client/src/test/resources/textfile2.txt similarity index 100% rename from api/src/test/resources/textfile2.txt rename to client/src/test/resources/textfile2.txt diff --git a/extras/guava/pom.xml b/extras/guava/pom.xml deleted file mode 100644 index c759c8ebe6..0000000000 --- a/extras/guava/pom.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - org.asynchttpclient - async-http-client-extras-parent - 2.0.0-SNAPSHOT - - 4.0.0 - async-http-client-extras-guava - Asynchronous Http Client Guava Extras - - The Async Http Client Guava Extras. - - - - - com.google.guava - guava - 14.0.1 - - - \ No newline at end of file diff --git a/extras/guava/src/main/java/org/asynchttpclient/extras/guava/ListenableFutureAdapter.java b/extras/guava/src/main/java/org/asynchttpclient/extras/guava/ListenableFutureAdapter.java deleted file mode 100644 index 9c51d29b68..0000000000 --- a/extras/guava/src/main/java/org/asynchttpclient/extras/guava/ListenableFutureAdapter.java +++ /dev/null @@ -1,57 +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.extras.guava; - -import org.asynchttpclient.ListenableFuture; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public final class ListenableFutureAdapter { - - /** - * @param future an AHC ListenableFuture - * @return a Guava ListenableFuture - */ - public static com.google.common.util.concurrent.ListenableFuture asGuavaFuture(final ListenableFuture future) { - - return new com.google.common.util.concurrent.ListenableFuture() { - - public boolean cancel(boolean mayInterruptIfRunning) { - return future.cancel(mayInterruptIfRunning); - } - - public V get() throws InterruptedException, ExecutionException { - return future.get(); - } - - public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - return future.get(timeout, unit); - } - - public boolean isCancelled() { - return future.isCancelled(); - } - - public boolean isDone() { - return future.isDone(); - } - - public void addListener(final Runnable runnable, final Executor executor) { - future.addListener(runnable, executor); - } - }; - } -} diff --git a/extras/guava/src/main/java/org/asynchttpclient/extras/guava/RateLimitedThrottleRequestFilter.java b/extras/guava/src/main/java/org/asynchttpclient/extras/guava/RateLimitedThrottleRequestFilter.java deleted file mode 100644 index dc07928d43..0000000000 --- a/extras/guava/src/main/java/org/asynchttpclient/extras/guava/RateLimitedThrottleRequestFilter.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.asynchttpclient.extras.guava; - -import org.asynchttpclient.extra.AsyncHandlerWrapper; -import org.asynchttpclient.extra.ThrottleRequestFilter; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.RequestFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.util.concurrent.RateLimiter; - -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -/** - * A {@link org.asynchttpclient.filter.RequestFilter} that extends the capability of - * {@link ThrottleRequestFilter} by allowing rate limiting per second in addition to the - * number of concurrent connections. - * - * The maxWaitMs argument is respected accross both permit acquistions. For - * example, if 1000 ms is given, and the filter spends 500 ms waiting for a connection, - * it will only spend another 500 ms waiting for the rate limiter. - */ -public class RateLimitedThrottleRequestFilter implements RequestFilter { - private final static Logger logger = LoggerFactory.getLogger(RateLimitedThrottleRequestFilter.class); - private final Semaphore available; - private final int maxWaitMs; - private final RateLimiter rateLimiter; - - public RateLimitedThrottleRequestFilter(int maxConnections, double rateLimitPerSecond) { - this(maxConnections, rateLimitPerSecond, Integer.MAX_VALUE); - } - - public RateLimitedThrottleRequestFilter(int maxConnections, double rateLimitPerSecond, int maxWaitMs) { - this.maxWaitMs = maxWaitMs; - this.rateLimiter = RateLimiter.create(rateLimitPerSecond); - available = new Semaphore(maxConnections, true); - } - - /** - * {@inheritDoc} - */ - @Override - public FilterContext filter(FilterContext ctx) throws FilterException { - try { - if (logger.isDebugEnabled()) { - logger.debug("Current Throttling Status {}", available.availablePermits()); - } - - long startOfWait = System.currentTimeMillis(); - attemptConcurrencyPermitAcquistion(ctx); - - attemptRateLimitedPermitAcquistion(ctx, startOfWait); - } catch (InterruptedException e) { - throw new FilterException(String.format("Interrupted Request %s with AsyncHandler %s", ctx.getRequest(), ctx.getAsyncHandler())); - } - - return new FilterContext.FilterContextBuilder(ctx).asyncHandler(new AsyncHandlerWrapper(ctx.getAsyncHandler(), available)) - .build(); - } - - private void attemptRateLimitedPermitAcquistion(FilterContext ctx, long startOfWait) throws FilterException { - long wait = getMillisRemainingInMaxWait(startOfWait); - - if (!rateLimiter.tryAcquire(wait, TimeUnit.MILLISECONDS)) { - throw new FilterException(String.format("Wait for rate limit exceeded during processing Request %s with AsyncHandler %s", - ctx.getRequest(), ctx.getAsyncHandler())); - } - } - - private void attemptConcurrencyPermitAcquistion(FilterContext ctx) throws InterruptedException, FilterException { - if (!available.tryAcquire(maxWaitMs, TimeUnit.MILLISECONDS)) { - throw new FilterException(String.format("No slot available for processing Request %s with AsyncHandler %s", ctx.getRequest(), - ctx.getAsyncHandler())); - } - } - - private long getMillisRemainingInMaxWait(long startOfWait) { - int MINUTE_IN_MILLIS = 60000; - long durationLeft = maxWaitMs - (System.currentTimeMillis() - startOfWait); - long nonNegativeDuration = Math.max(durationLeft, 0); - - // have to reduce the duration because there is a boundary case inside the Guava - // rate limiter where if the duration to wait is near Long.MAX_VALUE, the rate - // limiter's internal calculations can exceed Long.MAX_VALUE resulting in a - // negative number which causes the tryAcquire() method to fail unexpectedly - if (Long.MAX_VALUE - nonNegativeDuration < MINUTE_IN_MILLIS) { - return nonNegativeDuration - MINUTE_IN_MILLIS; - } - - return nonNegativeDuration; - } -} diff --git a/extras/jdeferred/pom.xml b/extras/jdeferred/pom.xml deleted file mode 100644 index 9945c60ad6..0000000000 --- a/extras/jdeferred/pom.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - 4.0.0 - - async-http-client-extras-parent - org.asynchttpclient - 2.0.0-SNAPSHOT - .. - - async-http-client-extras-jdeferred - Async Http Client JDeferred Extras - The Async Http Client jDeffered Extras. - - - org.jdeferred - jdeferred-core - 1.2.0 - - - diff --git a/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/AsyncHttpDeferredObject.java b/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/AsyncHttpDeferredObject.java deleted file mode 100644 index 9f50dc71cc..0000000000 --- a/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/AsyncHttpDeferredObject.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2013 Ray Tsang - * - * 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.extras.jdeferred; - -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.BoundRequestBuilder; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.Response; -import org.jdeferred.Promise; -import org.jdeferred.impl.DeferredObject; - -import java.io.IOException; - -public class AsyncHttpDeferredObject extends DeferredObject { - public AsyncHttpDeferredObject(BoundRequestBuilder builder) throws IOException { - builder.execute(new AsyncCompletionHandler() { - @Override - public Void onCompleted(Response response) throws Exception { - AsyncHttpDeferredObject.this.resolve(response); - return null; - } - - @Override - public void onThrowable(Throwable t) { - AsyncHttpDeferredObject.this.reject(t); - } - - @Override - public AsyncHandler.STATE onContentWriteProgress(long amount, long current, long total) { - AsyncHttpDeferredObject.this.notify(new ContentWriteProgress(amount, current, total)); - return super.onContentWriteProgress(amount, current, total); - } - - @Override - public AsyncHandler.STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { - AsyncHttpDeferredObject.this.notify(new HttpResponseBodyPartProgress(content)); - return super.onBodyPartReceived(content); - } - }); - } - - public static Promise promise(final BoundRequestBuilder builder) throws IOException { - return new AsyncHttpDeferredObject(builder).promise(); - } -} diff --git a/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/ContentWriteProgress.java b/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/ContentWriteProgress.java deleted file mode 100644 index b07a76d3f7..0000000000 --- a/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/ContentWriteProgress.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2013 Ray Tsang - * - * 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.extras.jdeferred; - -public class ContentWriteProgress implements HttpProgress { - private final long amount; - private final long current; - private final long total; - - public ContentWriteProgress(long amount, long current, long total) { - this.amount = amount; - this.current = current; - this.total = total; - } - - public long getAmount() { - return amount; - } - - public long getCurrent() { - return current; - } - - public long getTotal() { - return total; - } - - @Override - public String toString() { - return "ContentWriteProgress [amount=" + amount + ", current=" + current + ", total=" + total + "]"; - } -} diff --git a/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/HttpProgress.java b/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/HttpProgress.java deleted file mode 100644 index 8ff4788564..0000000000 --- a/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/HttpProgress.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2013 Ray Tsang - * - * 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.extras.jdeferred; - -public interface HttpProgress { -} diff --git a/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/HttpResponseBodyPartProgress.java b/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/HttpResponseBodyPartProgress.java deleted file mode 100644 index 7137c5469b..0000000000 --- a/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/HttpResponseBodyPartProgress.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2013 Ray Tsang - * - * 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.extras.jdeferred; - -import org.asynchttpclient.HttpResponseBodyPart; - -public class HttpResponseBodyPartProgress implements HttpProgress { - private final HttpResponseBodyPart part; - - public HttpResponseBodyPartProgress(HttpResponseBodyPart part) { - this.part = part; - } - - public HttpResponseBodyPart getPart() { - return part; - } - - @Override - public String toString() { - return "HttpResponseBodyPartProgress [part=" + part + "]"; - } -} diff --git a/extras/jdeferred/src/test/java/org/asynchttpclient/extra/AsyncHttpTest.java b/extras/jdeferred/src/test/java/org/asynchttpclient/extra/AsyncHttpTest.java deleted file mode 100644 index cb58470173..0000000000 --- a/extras/jdeferred/src/test/java/org/asynchttpclient/extra/AsyncHttpTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2013 Ray Tsang - * - * 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.extra; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.DefaultAsyncHttpClient; -import org.asynchttpclient.Response; -import org.asynchttpclient.extras.jdeferred.AsyncHttpDeferredObject; -import org.asynchttpclient.extras.jdeferred.HttpProgress; -import org.jdeferred.DoneCallback; -import org.jdeferred.ProgressCallback; -import org.jdeferred.Promise; -import org.jdeferred.impl.DefaultDeferredManager; -import org.jdeferred.multiple.MultipleResults; - -import java.io.IOException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicInteger; - -public class AsyncHttpTest { - protected DefaultDeferredManager deferredManager = new DefaultDeferredManager(); - - public void testPromiseAdapter() throws IOException { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicInteger successCount = new AtomicInteger(); - final AtomicInteger progressCount = new AtomicInteger(); - - AsyncHttpClient client = new DefaultAsyncHttpClient(); - - try { - Promise p1 = AsyncHttpDeferredObject.promise(client.prepareGet("http://www.ning.com/")); - p1.done(new DoneCallback() { - @Override - public void onDone(Response response) { - try { - assertEquals(response.getStatusCode(), 200); - successCount.incrementAndGet(); - } finally { - latch.countDown(); - } - } - }).progress(new ProgressCallback() { - - @Override - public void onProgress(HttpProgress progress) { - progressCount.incrementAndGet(); - } - }); - - latch.await(); - assertTrue(progressCount.get() > 0); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } finally { - client.close(); - } - } - - public void testMultiplePromiseAdapter() throws IOException { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicInteger successCount = new AtomicInteger(); - - AsyncHttpClient client = new DefaultAsyncHttpClient(); - - try { - Promise p1 = AsyncHttpDeferredObject.promise(client.prepareGet("http://www.ning.com/")); - Promise p2 = AsyncHttpDeferredObject.promise(client.prepareGet("http://www.google.com/")); - AsyncHttpDeferredObject deferredRequest = new AsyncHttpDeferredObject(client.prepareGet("http://jdeferred.org/")); - - deferredManager.when(p1, p2, deferredRequest).then(new DoneCallback() { - @Override - public void onDone(MultipleResults result) { - try { - assertEquals(result.size(), 3); - assertEquals(Response.class.cast(result.get(0).getResult()).getStatusCode(), 200); - assertEquals(Response.class.cast(result.get(1).getResult()).getStatusCode(), 200); - assertEquals(Response.class.cast(result.get(2).getResult()).getStatusCode(), 200); - successCount.incrementAndGet(); - } finally { - latch.countDown(); - } - } - }); - latch.await(); - - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } finally { - client.close(); - } - } -} diff --git a/extras/pom.xml b/extras/pom.xml deleted file mode 100644 index ab0b2dc70d..0000000000 --- a/extras/pom.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - org.asynchttpclient - async-http-client-project - 2.0.0-SNAPSHOT - - 4.0.0 - async-http-client-extras-parent - Asynchronous Http Client Extras Parent - pom - - The Async Http Client extras library parent. - - - - - - org.apache.felix - maven-bundle-plugin - 2.3.4 - true - - META-INF - - - $(replace;$(project.version);-SNAPSHOT;.$(tstamp;yyyyMMdd-HHmm)) - - Sonatype - - - - - osgi-bundle - package - - bundle - - - - - - - - - guava - jdeferred - registry - - - - - org.asynchttpclient - async-http-client-api - ${project.version} - - - org.asynchttpclient - async-http-client-api - ${project.version} - test - tests - - - \ No newline at end of file diff --git a/extras/registry/pom.xml b/extras/registry/pom.xml deleted file mode 100644 index 66119acdb0..0000000000 --- a/extras/registry/pom.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - org.asynchttpclient - async-http-client-extras-parent - 2.0.0-SNAPSHOT - - 4.0.0 - async-http-client-extras-registry - Asynchronous Http Client Registry Extras - - The Async Http Client Registry Extras. - - - - - - org.asynchttpclient - async-http-client-netty-provider - ${project.version} - test - - - org.asynchttpclient - async-http-client-grizzly-provider - ${project.version} - test - - - \ No newline at end of file diff --git a/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientFactory.java b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientFactory.java deleted file mode 100644 index 90457a924f..0000000000 --- a/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientFactory.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.DefaultAsyncHttpClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Constructor; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * The AsyncHttpClientFactory returns back an instance of AsyncHttpClient. The - * actual instance is determined by the system property - * 'org.async.http.client.impl'. If the system property doesn't exist then it - * checks for a property file 'asynchttpclient.properties' and looks for a - * property 'org.async.http.client.impl' in there. If it finds it then returns - * an instance of that class. If there is an exception while reading the - * properties file or system property it throws a RuntimeException - * AsyncHttpClientImplException. If any of the constructors of the instance - * throws an exception it thows a AsyncHttpClientImplException. By default if - * neither the system property or the property file exists then it will return - * the default instance of {@link DefaultAsyncHttpClient} - * - * @author sasurendran - * - */ -public class AsyncHttpClientFactory { - - private static Class asyncHttpClientImplClass = null; - private static volatile boolean instantiated = false; - public static final Logger logger = LoggerFactory.getLogger(AsyncHttpClientFactory.class); - private static Lock lock = new ReentrantLock(); - - public static AsyncHttpClient getAsyncHttpClient() { - - try { - if (attemptInstantiation()) - return (AsyncHttpClient) asyncHttpClientImplClass.newInstance(); - } catch (InstantiationException e) { - throw new AsyncHttpClientImplException("Unable to create the class specified by system property : " - + AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, e); - } catch (IllegalAccessException e) { - throw new AsyncHttpClientImplException("Unable to find the class specified by system property : " - + AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, e); - } - return new DefaultAsyncHttpClient(); - } - - public static AsyncHttpClient getAsyncHttpClient(AsyncHttpProvider provider) { - if (attemptInstantiation()) { - try { - Constructor constructor = asyncHttpClientImplClass.getConstructor(AsyncHttpProvider.class); - return constructor.newInstance(provider); - } catch (Exception e) { - throw new AsyncHttpClientImplException("Unable to find the instantiate the class specified by system property : " - + AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY + "(AsyncHttpProvider) due to : " + e.getMessage(), e); - } - } - return new DefaultAsyncHttpClient(provider); - } - - public static AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - if (attemptInstantiation()) { - try { - Constructor constructor = asyncHttpClientImplClass.getConstructor(AsyncHttpClientConfig.class); - return constructor.newInstance(config); - } catch (Exception e) { - throw new AsyncHttpClientImplException("Unable to find the instantiate the class specified by system property : " - + AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY + "(AsyncHttpProvider) due to : " + e.getMessage(), e); - } - } - return new DefaultAsyncHttpClient(config); - } - - public static AsyncHttpClient getAsyncHttpClient(AsyncHttpProvider provider, AsyncHttpClientConfig config) { - if (attemptInstantiation()) { - try { - Constructor constructor = asyncHttpClientImplClass.getConstructor(AsyncHttpProvider.class, - AsyncHttpClientConfig.class); - return constructor.newInstance(provider, config); - } catch (Exception e) { - throw new AsyncHttpClientImplException("Unable to find the instantiate the class specified by system property : " - + AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY + "(AsyncHttpProvider) due to : " + e.getMessage(), e); - } - } - return new DefaultAsyncHttpClient(provider, config); - } - - public static AsyncHttpClient getAsyncHttpClient(String providerClass, AsyncHttpClientConfig config) { - if (attemptInstantiation()) { - try { - Constructor constructor = asyncHttpClientImplClass.getConstructor(String.class, - AsyncHttpClientConfig.class); - return constructor.newInstance(providerClass, config); - } catch (Exception e) { - throw new AsyncHttpClientImplException("Unable to find the instantiate the class specified by system property : " - + AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY + "(AsyncHttpProvider) due to : " + e.getMessage(), e); - } - } - return new DefaultAsyncHttpClient(providerClass, config); - } - - private static boolean attemptInstantiation() { - if (!instantiated) { - lock.lock(); - try { - if (!instantiated) { - asyncHttpClientImplClass = AsyncImplHelper.getAsyncImplClass(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY); - instantiated = true; - } - } finally { - lock.unlock(); - } - } - return asyncHttpClientImplClass != null; - } -} diff --git a/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientImplException.java b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientImplException.java deleted file mode 100644 index f59bf0698c..0000000000 --- a/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientImplException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -@SuppressWarnings("serial") -public class AsyncHttpClientImplException extends RuntimeException { - - public AsyncHttpClientImplException(String msg) { - super(msg); - } - - public AsyncHttpClientImplException(String msg, Exception e) { - super(msg, e); - } -} diff --git a/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistry.java b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistry.java deleted file mode 100644 index ca63009ce7..0000000000 --- a/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistry.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -import org.asynchttpclient.AsyncHttpClient; - -import java.util.Set; - -public interface AsyncHttpClientRegistry { - - /** - * Returns back the AsyncHttpClient associated with this name - * - * @param clientName - * @return - */ - AsyncHttpClient get(String clientName); - - /** - * Registers this instance of AsyncHttpClient with this name and returns - * back a null if an instance with the same name never existed but will return back the - * previous instance if there was another instance registered with the same - * name and has been replaced by this one. - * - * @param name - * @param ahc - * @return - */ - AsyncHttpClient addOrReplace(String name, AsyncHttpClient ahc); - - /** - * Will register only if an instance with this name doesn't exist and if it - * does exist will not replace this instance and will return false. Use it in the - * following way: - *
-     *      AsyncHttpClient ahc = AsyncHttpClientFactory.getAsyncHttpClient();      
-     *      if(!AsyncHttpClientRegistryImpl.getInstance().registerIfNew(“MyAHC”,ahc)){
-     *          //An instance with this name is already registered so close ahc 
-     *          ahc.close(); 
-     *          //and do necessary cleanup
-     *      }
-     * 
- * - * @param name - * @param ahc - * @return - */ - - boolean registerIfNew(String name, AsyncHttpClient ahc); - - /** - * Remove the instance associate with this name - * - * @param name - * @return - */ - - boolean unRegister(String name); - - /** - * Returns back all registered names - * - * @return - */ - - Set getAllRegisteredNames(); - - /** - * Removes all instances from this registry. - */ - - void clearAllInstances(); -} diff --git a/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryImpl.java b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryImpl.java deleted file mode 100644 index 0c13c2e585..0000000000 --- a/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryImpl.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -import org.asynchttpclient.AsyncHttpClient; - -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -public class AsyncHttpClientRegistryImpl implements AsyncHttpClientRegistry { - - private static ConcurrentMap asyncHttpClientMap = new ConcurrentHashMap(); - private static volatile AsyncHttpClientRegistry _instance; - private static Lock lock = new ReentrantLock(); - - /** - * Returns a singleton instance of AsyncHttpClientRegistry - * @return - */ - public static AsyncHttpClientRegistry getInstance() { - if (_instance == null) { - lock.lock(); - try { - if (_instance == null) { - Class asyncHttpClientRegistryImplClass = AsyncImplHelper - .getAsyncImplClass(AsyncImplHelper.ASYNC_HTTP_CLIENT_REGISTRY_SYSTEM_PROPERTY); - if (asyncHttpClientRegistryImplClass != null) - _instance = (AsyncHttpClientRegistry) asyncHttpClientRegistryImplClass.newInstance(); - else - _instance = new AsyncHttpClientRegistryImpl(); - } - } catch (InstantiationException e) { - throw new AsyncHttpClientImplException("Couldn't instantiate AsyncHttpClientRegistry : " + e.getMessage(), e); - } catch (IllegalAccessException e) { - throw new AsyncHttpClientImplException("Couldn't instantiate AsyncHttpClientRegistry : " + e.getMessage(), e); - } finally { - lock.unlock(); - } - } - return _instance; - } - - /* - * (non-Javadoc) - * - * @see org.asynchttpclient.IAsyncHttpClientRegistry#get(java.lang.String) - */ - @Override - public AsyncHttpClient get(String clientName) { - return asyncHttpClientMap.get(clientName); - } - - /* - * (non-Javadoc) - * - * @see - * org.asynchttpclient.IAsyncHttpClientRegistry#register(java.lang.String, - * org.asynchttpclient.AsyncHttpClient) - */ - @Override - public AsyncHttpClient addOrReplace(String name, AsyncHttpClient ahc) { - return asyncHttpClientMap.put(name, ahc); - } - - /* - * (non-Javadoc) - * - * @see - * org.asynchttpclient.IAsyncHttpClientRegistry#registerIfNew(java.lang. - * String, org.asynchttpclient.AsyncHttpClient) - */ - @Override - public boolean registerIfNew(String name, AsyncHttpClient ahc) { - return asyncHttpClientMap.putIfAbsent(name, ahc) == null; - } - - /* - * (non-Javadoc) - * - * @see - * org.asynchttpclient.IAsyncHttpClientRegistry#unRegister(java.lang.String) - */ - @Override - public boolean unRegister(String name) { - return asyncHttpClientMap.remove(name) != null; - } - - /* - * (non-Javadoc) - * - * @see org.asynchttpclient.IAsyncHttpClientRegistry#getAllRegisteredNames() - */ - @Override - public Set getAllRegisteredNames() { - return asyncHttpClientMap.keySet(); - } - - /* - * (non-Javadoc) - * - * @see org.asynchttpclient.IAsyncHttpClientRegistry#clearAllInstances() - */ - @Override - public void clearAllInstances() { - asyncHttpClientMap.clear(); - } -} diff --git a/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncImplHelper.java b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncImplHelper.java deleted file mode 100644 index 0e827c0114..0000000000 --- a/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncImplHelper.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -import org.asynchttpclient.AsyncHttpClient; - -import java.io.IOException; -import java.io.InputStream; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.Properties; - -public class AsyncImplHelper { - - public static final String ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY = "org.async.http.client.impl"; - public static final String ASYNC_HTTP_CLIENT_REGISTRY_SYSTEM_PROPERTY = "org.async.http.client.registry.impl"; - public static final String ASYNC_HTTP_CLIENT_IMPL_PROPERTIES_FILE = "asynchttpclient.properties"; - - private static String getSystemProperty(final String systemProperty) { - return AccessController.doPrivileged(new PrivilegedAction() { - public String run() { - return System.getProperty(systemProperty); - } - }); - } - - /* - * Returns the class specified by either a system property or a properties - * file as the class to instantiated for the AsyncHttpClient. Returns null - * if property is not found and throws an AsyncHttpClientImplException if - * the specified class couldn't be created. - */ - public static Class getAsyncImplClass(String propertyName) { - String asyncHttpClientImplClassName = getSystemProperty(propertyName); - if (asyncHttpClientImplClassName == null) { - Properties properties = AsyncImplHelper.getAsyncImplProperties(); - if (properties != null) - asyncHttpClientImplClassName = properties.getProperty(propertyName); - } - - if (asyncHttpClientImplClassName == null) - return null; - - Class asyncHttpClientImplClass = AsyncImplHelper.getClass(asyncHttpClientImplClassName); - return asyncHttpClientImplClass; - } - - private static Properties getAsyncImplProperties() { - try { - return AccessController.doPrivileged(new PrivilegedExceptionAction() { - public Properties run() throws IOException { - InputStream stream = null; - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - if (cl != null) - stream = cl.getResourceAsStream(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_PROPERTIES_FILE); - if (stream == null) - stream = ClassLoader.getSystemClassLoader().getResourceAsStream( - AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_PROPERTIES_FILE); - if (stream != null) { - Properties properties = new Properties(); - properties.load(stream); - return properties; - } - return null; - } - }); - } catch (PrivilegedActionException e) { - throw new AsyncHttpClientImplException("Unable to read properties file because of exception : " + e.getMessage(), e); - } - } - - private static Class getClass(final String asyncImplClassName) { - try { - return AccessController.doPrivileged(new PrivilegedExceptionAction>() { - @SuppressWarnings("unchecked") - public Class run() throws ClassNotFoundException { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - if (cl != null) - try { - return (Class) cl.loadClass(asyncImplClassName); - } catch (ClassNotFoundException e) { - AsyncHttpClientFactory.logger.info("Couldn't find class : " + asyncImplClassName - + " in thread context classpath " + "checking system class path next", e); - } - - cl = ClassLoader.getSystemClassLoader(); - return (Class) cl.loadClass(asyncImplClassName); - } - }); - } catch (PrivilegedActionException e) { - throw new AsyncHttpClientImplException("Class : " + asyncImplClassName + " couldn't be found in " + " the classpath due to : " - + e.getMessage(), e); - } - } -} diff --git a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AbstractAsyncHttpClientFactoryTest.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AbstractAsyncHttpClientFactoryTest.java deleted file mode 100644 index b4c05cc8be..0000000000 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AbstractAsyncHttpClientFactoryTest.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.DefaultAsyncHttpClient; -import org.asynchttpclient.Response; -import org.asynchttpclient.async.util.EchoHandler; -import org.asynchttpclient.async.util.TestUtils; -import org.asynchttpclient.extras.registry.AsyncHttpClientFactory; -import org.asynchttpclient.extras.registry.AsyncHttpClientImplException; -import org.asynchttpclient.extras.registry.AsyncImplHelper; -import org.eclipse.jetty.server.Server; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.lang.reflect.InvocationTargetException; - -import junit.extensions.PA; - -public abstract class AbstractAsyncHttpClientFactoryTest { - - public static final String TEST_CLIENT_CLASS_NAME = "org.asynchttpclient.extras.registry.TestAsyncHttpClient"; - public static final String BAD_CLIENT_CLASS_NAME = "org.asynchttpclient.extras.registry.BadAsyncHttpClient"; - public static final String NON_EXISTENT_CLIENT_CLASS_NAME = "org.asynchttpclient.extras.registry.NonExistentAsyncHttpClient"; - - private Server server; - private int port; - - @BeforeMethod - public void setUp() { - PA.setValue(AsyncHttpClientFactory.class, "instantiated", false); - PA.setValue(AsyncHttpClientFactory.class, "asyncHttpClientImplClass", null); - System.clearProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY); - } - - @BeforeClass(alwaysRun = true) - public void setUpBeforeTest() throws Exception { - port = TestUtils.findFreePort(); - server = TestUtils.newJettyHttpServer(port); - server.setHandler(new EchoHandler()); - server.start(); - } - - @AfterClass(alwaysRun = true) - public void tearDown() throws Exception { - setUp(); - if (server != null) - server.stop(); - } - - public abstract AsyncHttpProvider getAsyncHttpProvider(AsyncHttpClientConfig config); - - /** - * If the property is not found via the system property or properties file - * the default instance of AsyncHttpClient should be returned. - */ - // ================================================================================================================ - @Test(groups = "fast") - public void testGetAsyncHttpClient() { - AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(); - Assert.assertTrue(asyncHttpClient.getClass().equals(DefaultAsyncHttpClient.class)); - assertClientWorks(asyncHttpClient); - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientConfig() { - AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(new AsyncHttpClientConfig.Builder().build()); - Assert.assertTrue(asyncHttpClient.getClass().equals(DefaultAsyncHttpClient.class)); - assertClientWorks(asyncHttpClient); - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientProvider() { - AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null)); - Assert.assertTrue(asyncHttpClient.getClass().equals(DefaultAsyncHttpClient.class)); - assertClientWorks(asyncHttpClient); - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientConfigAndProvider() { - AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null), - new AsyncHttpClientConfig.Builder().build()); - Assert.assertTrue(asyncHttpClient.getClass().equals(DefaultAsyncHttpClient.class)); - assertClientWorks(asyncHttpClient); - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientStringConfig() { - AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null).getClass().getName(), - new AsyncHttpClientConfig.Builder().build()); - Assert.assertTrue(asyncHttpClient.getClass().equals(DefaultAsyncHttpClient.class)); - assertClientWorks(asyncHttpClient); - } - - // ================================================================================================================================== - - /** - * If the class is specified via a system property then that class should be - * returned - */ - // =================================================================================================================================== - @Test(groups = "fast") - public void testFactoryWithSystemProperty() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, TEST_CLIENT_CLASS_NAME); - Assert.assertTrue(AsyncHttpClientFactory.getAsyncHttpClient().getClass().equals(TestAsyncHttpClient.class)); - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientConfigWithSystemProperty() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, TEST_CLIENT_CLASS_NAME); - AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(new AsyncHttpClientConfig.Builder().build()); - Assert.assertTrue(asyncHttpClient.getClass().equals(TestAsyncHttpClient.class)); - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientProviderWithSystemProperty() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, TEST_CLIENT_CLASS_NAME); - AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null)); - Assert.assertTrue(asyncHttpClient.getClass().equals(TestAsyncHttpClient.class)); - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientConfigAndProviderWithSystemProperty() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, TEST_CLIENT_CLASS_NAME); - AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null), - new AsyncHttpClientConfig.Builder().build()); - Assert.assertTrue(asyncHttpClient.getClass().equals(TestAsyncHttpClient.class)); - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientStringConfigWithSystemProperty() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, TEST_CLIENT_CLASS_NAME); - AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null).getClass().getName(), - new AsyncHttpClientConfig.Builder().build()); - Assert.assertTrue(asyncHttpClient.getClass().equals(TestAsyncHttpClient.class)); - } - - // =================================================================================================================================== - - /** - * If any of the constructors of the class fail then a - * AsyncHttpClientException is thrown. - */ - // =================================================================================================================================== - @Test(groups = "fast", expectedExceptions = BadAsyncHttpClientException.class) - public void testFactoryWithBadAsyncHttpClient() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, BAD_CLIENT_CLASS_NAME); - AsyncHttpClientFactory.getAsyncHttpClient(); - Assert.fail("BadAsyncHttpClientException should have been thrown before this point"); - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientConfigWithBadAsyncHttpClient() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, BAD_CLIENT_CLASS_NAME); - try { - AsyncHttpClientFactory.getAsyncHttpClient(new AsyncHttpClientConfig.Builder().build()); - } catch (AsyncHttpClientImplException e) { - assertException(e); - } - //Assert.fail("AsyncHttpClientImplException should have been thrown before this point"); - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientProviderWithBadAsyncHttpClient() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, BAD_CLIENT_CLASS_NAME); - try { - AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null)); - } catch (AsyncHttpClientImplException e) { - assertException(e); - } - //Assert.fail("AsyncHttpClientImplException should have been thrown before this point"); - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientConfigAndProviderWithBadAsyncHttpClient() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, BAD_CLIENT_CLASS_NAME); - try { - AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null), new AsyncHttpClientConfig.Builder().build()); - } catch (AsyncHttpClientImplException e) { - assertException(e); - } - //Assert.fail("AsyncHttpClientImplException should have been thrown before this point"); - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientStringConfigWithBadAsyncHttpClient() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, BAD_CLIENT_CLASS_NAME); - try { - AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null).getClass().getName(), - new AsyncHttpClientConfig.Builder().build()); - } catch (AsyncHttpClientImplException e) { - assertException(e); - } - //Assert.fail("AsyncHttpClientImplException should have been thrown before this point"); - } - - // =================================================================================================================================== - - /* - * If the system property exists instantiate the class else if the class is - * not found throw an AsyncHttpClientException. - */ - @Test(groups = "fast", expectedExceptions = AsyncHttpClientImplException.class) - public void testFactoryWithNonExistentAsyncHttpClient() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, NON_EXISTENT_CLIENT_CLASS_NAME); - AsyncHttpClientFactory.getAsyncHttpClient(); - Assert.fail("AsyncHttpClientImplException should have been thrown before this point"); - } - - /** - * If property is specified but the class can’t be created or found for any - * reason subsequent calls should throw an AsyncClientException. - */ - @Test(groups = "fast", expectedExceptions = AsyncHttpClientImplException.class) - public void testRepeatedCallsToBadAsyncHttpClient() { - boolean exceptionCaught = false; - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, NON_EXISTENT_CLIENT_CLASS_NAME); - try { - AsyncHttpClientFactory.getAsyncHttpClient(); - } catch (AsyncHttpClientImplException e) { - exceptionCaught = true; - } - Assert.assertTrue(exceptionCaught, "Didn't catch exception the first time"); - exceptionCaught = false; - try { - AsyncHttpClientFactory.getAsyncHttpClient(new AsyncHttpClientConfig.Builder().build()); - } catch (AsyncHttpClientImplException e) { - exceptionCaught = true; - } - Assert.assertTrue(exceptionCaught, "Didn't catch exception the second time"); - AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null).getClass().getName(), - new AsyncHttpClientConfig.Builder().build()); - - } - - private void assertClientWorks(AsyncHttpClient asyncHttpClient) { - Response response; - try { - response = asyncHttpClient.prepareGet("http://localhost/" + port + "/foo/test").execute().get(); - Assert.assertEquals(200, response.getStatusCode()); - } catch (Exception e) { - Assert.fail("Failed while making call with AsyncHttpClient", e); - } finally { - asyncHttpClient.close(); - } - } - - private void assertException(AsyncHttpClientImplException e) { - InvocationTargetException t = (InvocationTargetException) e.getCause(); - Assert.assertTrue(t.getCause() instanceof BadAsyncHttpClientException); - } - -} diff --git a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryTest.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryTest.java deleted file mode 100644 index c4e94ed128..0000000000 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.extras.registry.AsyncHttpClientFactory; -import org.asynchttpclient.extras.registry.AsyncHttpClientImplException; -import org.asynchttpclient.extras.registry.AsyncHttpClientRegistryImpl; -import org.asynchttpclient.extras.registry.AsyncImplHelper; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import junit.extensions.PA; - -public class AsyncHttpClientRegistryTest { - - private static final String TEST_AHC = "testAhc"; - - @BeforeMethod - public void setUp() { - System.clearProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_REGISTRY_SYSTEM_PROPERTY); - AsyncHttpClientRegistryImpl.getInstance().clearAllInstances(); - PA.setValue(AsyncHttpClientRegistryImpl.class, "_instance", null); - } - - @BeforeClass - public void setUpBeforeTest() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, AbstractAsyncHttpClientFactoryTest.TEST_CLIENT_CLASS_NAME); - } - - @AfterClass - public void tearDown() { - System.clearProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY); - } - - @Test(groups = "fast") - public void testGetAndRegister() { - AsyncHttpClient ahc = AsyncHttpClientFactory.getAsyncHttpClient(); - Assert.assertNull(AsyncHttpClientRegistryImpl.getInstance().get(TEST_AHC)); - Assert.assertNull(AsyncHttpClientRegistryImpl.getInstance().addOrReplace(TEST_AHC, ahc)); - Assert.assertNotNull(AsyncHttpClientRegistryImpl.getInstance().get(TEST_AHC)); - } - - @Test(groups = "fast") - public void testDeRegister() { - AsyncHttpClient ahc = AsyncHttpClientFactory.getAsyncHttpClient(); - Assert.assertFalse(AsyncHttpClientRegistryImpl.getInstance().unRegister(TEST_AHC)); - Assert.assertNull(AsyncHttpClientRegistryImpl.getInstance().addOrReplace(TEST_AHC, ahc)); - Assert.assertTrue(AsyncHttpClientRegistryImpl.getInstance().unRegister(TEST_AHC)); - Assert.assertNull(AsyncHttpClientRegistryImpl.getInstance().get(TEST_AHC)); - } - - @Test(groups = "fast") - public void testRegisterIfNew() { - AsyncHttpClient ahc = AsyncHttpClientFactory.getAsyncHttpClient(); - AsyncHttpClient ahc2 = AsyncHttpClientFactory.getAsyncHttpClient(); - Assert.assertNull(AsyncHttpClientRegistryImpl.getInstance().addOrReplace(TEST_AHC, ahc)); - Assert.assertFalse(AsyncHttpClientRegistryImpl.getInstance().registerIfNew(TEST_AHC, ahc2)); - Assert.assertTrue(AsyncHttpClientRegistryImpl.getInstance().get(TEST_AHC) == ahc); - Assert.assertNotNull(AsyncHttpClientRegistryImpl.getInstance().addOrReplace(TEST_AHC, ahc2)); - Assert.assertTrue(AsyncHttpClientRegistryImpl.getInstance().get(TEST_AHC) == ahc2); - Assert.assertTrue(AsyncHttpClientRegistryImpl.getInstance().registerIfNew(TEST_AHC + 1, ahc)); - Assert.assertTrue(AsyncHttpClientRegistryImpl.getInstance().get(TEST_AHC + 1) == ahc); - } - - @Test(groups = "fast") - public void testClearAllInstances() { - AsyncHttpClient ahc = AsyncHttpClientFactory.getAsyncHttpClient(); - AsyncHttpClient ahc2 = AsyncHttpClientFactory.getAsyncHttpClient(); - AsyncHttpClient ahc3 = AsyncHttpClientFactory.getAsyncHttpClient(); - Assert.assertNull(AsyncHttpClientRegistryImpl.getInstance().addOrReplace(TEST_AHC, ahc)); - Assert.assertNull(AsyncHttpClientRegistryImpl.getInstance().addOrReplace(TEST_AHC + 2, ahc2)); - Assert.assertNull(AsyncHttpClientRegistryImpl.getInstance().addOrReplace(TEST_AHC + 3, ahc3)); - Assert.assertEquals(3, AsyncHttpClientRegistryImpl.getInstance().getAllRegisteredNames().size()); - AsyncHttpClientRegistryImpl.getInstance().clearAllInstances(); - Assert.assertEquals(0, AsyncHttpClientRegistryImpl.getInstance().getAllRegisteredNames().size()); - Assert.assertNull(AsyncHttpClientRegistryImpl.getInstance().get(TEST_AHC)); - Assert.assertNull(AsyncHttpClientRegistryImpl.getInstance().get(TEST_AHC + 2)); - Assert.assertNull(AsyncHttpClientRegistryImpl.getInstance().get(TEST_AHC + 3)); - } - - @Test(groups = "fast") - public void testCustomAsyncHttpClientRegistry() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_REGISTRY_SYSTEM_PROPERTY, TestAsyncHttpClientRegistry.class.getName()); - Assert.assertTrue(AsyncHttpClientRegistryImpl.getInstance() instanceof TestAsyncHttpClientRegistry); - } - - @Test(groups = "fast", expectedExceptions = AsyncHttpClientImplException.class) - public void testNonExistentAsyncHttpClientRegistry() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_REGISTRY_SYSTEM_PROPERTY, AbstractAsyncHttpClientFactoryTest.NON_EXISTENT_CLIENT_CLASS_NAME); - AsyncHttpClientRegistryImpl.getInstance(); - Assert.fail("Should never have reached here"); - } - - @Test(groups = "fast", expectedExceptions = AsyncHttpClientImplException.class) - public void testBadAsyncHttpClientRegistry() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_REGISTRY_SYSTEM_PROPERTY, AbstractAsyncHttpClientFactoryTest.BAD_CLIENT_CLASS_NAME); - AsyncHttpClientRegistryImpl.getInstance(); - Assert.fail("Should never have reached here"); - } - -} diff --git a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClient.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClient.java deleted file mode 100644 index 84343ec0d4..0000000000 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClient.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.BoundRequestBuilder; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.Request; -import org.asynchttpclient.Response; -import org.asynchttpclient.SignatureCalculator; - -import java.io.IOException; - -public class BadAsyncHttpClient implements AsyncHttpClient { - - public BadAsyncHttpClient() { - throw new BadAsyncHttpClientException("Because I am bad!!"); - } - - public BadAsyncHttpClient(AsyncHttpProvider provider) { - throw new BadAsyncHttpClientException("Because I am bad!!"); - } - - public BadAsyncHttpClient(AsyncHttpClientConfig config) { - throw new BadAsyncHttpClientException("Because I am bad!!"); - } - - public BadAsyncHttpClient(String providerClass, AsyncHttpClientConfig config) { - throw new BadAsyncHttpClientException("Because I am bad!!"); - } - - public BadAsyncHttpClient(AsyncHttpProvider httpProvider, AsyncHttpClientConfig config) { - throw new BadAsyncHttpClientException("Because I am bad!!"); - } - - @Override - public AsyncHttpProvider getProvider() { - return null; - } - - @Override - public void close() { - - } - - @Override - public void closeAsynchronously() { - - } - - @Override - public boolean isClosed() { - return false; - } - - @Override - public AsyncHttpClientConfig getConfig() { - return null; - } - - @Override - public AsyncHttpClient setSignatureCalculator(SignatureCalculator signatureCalculator) { - return null; - } - - @Override - public BoundRequestBuilder prepareGet(String url) { - return null; - } - - @Override - public BoundRequestBuilder prepareConnect(String url) { - return null; - } - - @Override - public BoundRequestBuilder prepareOptions(String url) { - return null; - } - - @Override - public BoundRequestBuilder prepareHead(String url) { - return null; - } - - @Override - public BoundRequestBuilder preparePost(String url) { - return null; - } - - @Override - public BoundRequestBuilder preparePut(String url) { - return null; - } - - @Override - public BoundRequestBuilder prepareDelete(String url) { - return null; - } - - @Override - public BoundRequestBuilder preparePatch(String url) { - return null; - } - - @Override - public BoundRequestBuilder prepareTrace(String url) { - return null; - } - - @Override - public BoundRequestBuilder prepareRequest(Request request) { - return null; - } - - @Override - public ListenableFuture executeRequest(Request request, AsyncHandler handler) throws IOException { - return null; - } - - @Override - public ListenableFuture executeRequest(Request request) throws IOException { - return null; - } - -} diff --git a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClientException.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClientException.java deleted file mode 100644 index 1aca098e89..0000000000 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClientException.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -import org.asynchttpclient.extras.registry.AsyncHttpClientImplException; - -@SuppressWarnings("serial") -public class BadAsyncHttpClientException extends AsyncHttpClientImplException { - - public BadAsyncHttpClientException(String msg) { - super(msg); - } -} diff --git a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClientRegistry.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClientRegistry.java deleted file mode 100644 index b3d853de3f..0000000000 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClientRegistry.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -import org.asynchttpclient.extras.registry.AsyncHttpClientRegistryImpl; - -public class BadAsyncHttpClientRegistry extends AsyncHttpClientRegistryImpl { - - private BadAsyncHttpClientRegistry() { - throw new RuntimeException("I am bad"); - } -} diff --git a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/GrizzlyAsyncHttpClientFactoryTest.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/GrizzlyAsyncHttpClientFactoryTest.java deleted file mode 100644 index cfa9c9d4bd..0000000000 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/GrizzlyAsyncHttpClientFactoryTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.extras.registry.AbstractAsyncHttpClientFactoryTest; -import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; -import org.testng.annotations.Test; - -@Test -public class GrizzlyAsyncHttpClientFactoryTest extends AbstractAsyncHttpClientFactoryTest { - - @Override - public AsyncHttpProvider getAsyncHttpProvider(AsyncHttpClientConfig config) { - if (config == null) { - config = new AsyncHttpClientConfig.Builder().build(); - } - return new GrizzlyAsyncHttpProvider(config); - } -} diff --git a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/NettyAsyncHttpClientFactoryTest.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/NettyAsyncHttpClientFactoryTest.java deleted file mode 100644 index a5cc1fa028..0000000000 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/NettyAsyncHttpClientFactoryTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.extras.registry.AbstractAsyncHttpClientFactoryTest; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProvider; -import org.testng.annotations.Test; - -@Test -public class NettyAsyncHttpClientFactoryTest extends AbstractAsyncHttpClientFactoryTest { - - @Override - public AsyncHttpProvider getAsyncHttpProvider(AsyncHttpClientConfig config) { - if (config == null) { - config = new AsyncHttpClientConfig.Builder().build(); - } - return new NettyAsyncHttpProvider(config); - } -} diff --git a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/TestAsyncHttpClient.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/TestAsyncHttpClient.java deleted file mode 100644 index 9d927287fc..0000000000 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/TestAsyncHttpClient.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.BoundRequestBuilder; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.Request; -import org.asynchttpclient.Response; -import org.asynchttpclient.SignatureCalculator; - -import java.io.IOException; - -public class TestAsyncHttpClient implements AsyncHttpClient { - - public TestAsyncHttpClient() { - } - - public TestAsyncHttpClient(AsyncHttpProvider provider) { - } - - public TestAsyncHttpClient(AsyncHttpClientConfig config) { - } - - public TestAsyncHttpClient(String providerClass, AsyncHttpClientConfig config) { - } - - public TestAsyncHttpClient(AsyncHttpProvider httpProvider, AsyncHttpClientConfig config) { - } - - @Override - public AsyncHttpProvider getProvider() { - return null; - } - - @Override - public void close() { - } - - @Override - public void closeAsynchronously() { - } - - @Override - public boolean isClosed() { - return false; - } - - @Override - public AsyncHttpClientConfig getConfig() { - return null; - } - - @Override - public AsyncHttpClient setSignatureCalculator(SignatureCalculator signatureCalculator) { - return null; - } - - @Override - public BoundRequestBuilder prepareGet(String url) { - return null; - } - - @Override - public BoundRequestBuilder prepareConnect(String url) { - return null; - } - - @Override - public BoundRequestBuilder prepareOptions(String url) { - return null; - } - - @Override - public BoundRequestBuilder prepareHead(String url) { - return null; - } - - @Override - public BoundRequestBuilder preparePost(String url) { - return null; - } - - @Override - public BoundRequestBuilder preparePut(String url) { - return null; - } - - @Override - public BoundRequestBuilder prepareDelete(String url) { - return null; - } - - @Override - public BoundRequestBuilder preparePatch(String url) { - return null; - } - - @Override - public BoundRequestBuilder prepareTrace(String url) { - return null; - } - - @Override - public BoundRequestBuilder prepareRequest(Request request) { - return null; - } - - @Override - public ListenableFuture executeRequest(Request request, AsyncHandler handler) throws IOException { - return null; - } - - @Override - public ListenableFuture executeRequest(Request request) throws IOException { - return null; - } - -} diff --git a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/TestAsyncHttpClientRegistry.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/TestAsyncHttpClientRegistry.java deleted file mode 100644 index b9410737d5..0000000000 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/TestAsyncHttpClientRegistry.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2010-2014 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.extras.registry; - -import org.asynchttpclient.extras.registry.AsyncHttpClientRegistryImpl; - -public class TestAsyncHttpClientRegistry extends AsyncHttpClientRegistryImpl { - -} diff --git a/extras/registry/src/test/resources/300k.png b/extras/registry/src/test/resources/300k.png deleted file mode 100644 index bff4a85989..0000000000 Binary files a/extras/registry/src/test/resources/300k.png and /dev/null differ diff --git a/extras/registry/src/test/resources/SimpleTextFile.txt b/extras/registry/src/test/resources/SimpleTextFile.txt deleted file mode 100644 index 088788f821..0000000000 --- a/extras/registry/src/test/resources/SimpleTextFile.txt +++ /dev/null @@ -1 +0,0 @@ -This is a simple test file \ No newline at end of file diff --git a/mvnw b/mvnw new file mode 100755 index 0000000000..5643201c7d --- /dev/null +++ b/mvnw @@ -0,0 +1,316 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# 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 +# +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# 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 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + 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" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +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 + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +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" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +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. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + 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) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + 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" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +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" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +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 new file mode 100644 index 0000000000..23b7079a3d --- /dev/null +++ b/mvnw.cmd @@ -0,0 +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 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 a4e54d8ded..e55fe8a26b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,539 +1,459 @@ + - - org.sonatype.oss - oss-parent - 5 - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0%20http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + org.asynchttpclient async-http-client-project - Asynchronous Http Client Project - 2.0.0-SNAPSHOT + 3.0.2 pom + + AHC/Project The Async Http Client (AHC) library's purpose is to allow Java applications to easily execute HTTP requests and asynchronously process the response. - http://github.com/AsyncHttpClient/async-http-client + + https://github.com/AsyncHttpClient/async-http-client + + + + The Apache Software License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + + + + + 11 + 11 + 11 + UTF-8 + + 4.1.119.Final + 0.0.26.Final + 1.18.0 + 2.0.16 + 1.5.7-2 + 2.0.1 + 1.5.18 + 26.0.2 + + + + + hyperxpro + Aayush Atharva + aayush@shieldblaze.com + + + - scm:git:git@github.com:AsyncHttpClient/async-http-client.git - - https://github.com/AsyncHttpClient/async-http-client - - scm:git:git@github.com:AsyncHttpClient/async-http-client.git - + scm:git:git@github.com:AsyncHttpClient/async-http-client.git + scm:git:git@github.com:AsyncHttpClient/async-http-client.git + https://github.com/AsyncHttpClient/async-http-client/tree/master + HEAD + + + + sonatype-nexus-staging + https://oss.sonatype.org/content/repositories/snapshots + + + sonatype-nexus-staging + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + - jira - https://issues.sonatype.org/browse/AHC + github + https://github.com/AsyncHttpClient/async-http-client/issues + asynchttpclient - http://groups.google.com/group/asynchttpclient/topics - - - http://groups.google.com/group/asynchttpclient/subscribe - - - http://groups.google.com/group/asynchttpclient/subscribe - + https://groups.google.com/group/asynchttpclient/topics + https://groups.google.com/group/asynchttpclient/subscribe + https://groups.google.com/group/asynchttpclient/subscribe asynchttpclient@googlegroups.com - - 2.0.9 - - - - brianm - Brian McCallister - brianm@skife.org - - - jfarcand - Jeanfrancois Arcand - jfarcand@apache.org - - - thomd - Thomas Dudziak - tomdz@apache.org - - - neotyk - Hubert Iwaniuk - - - rlubke - Ryan Lubke - ryan.lubke@gmail.com - - - slandelle - Stephane Landelle - slandelle@excilys.com - - - - - Simone Tripodi - simonetripodi@apache.org - - - - - Apache License 2.0 - http://www.apache.org/licenses/LICENSE-2.0.html - repo - - + + client + + + + + + org.junit + junit-bom + 5.11.4 + pom + import + + + io.github.nettyplus + netty-leak-detector-junit-extension + 0.2.0 + + + + + + + io.netty + netty-buffer + ${netty.version} + + + + io.netty + netty-codec-http + ${netty.version} + + + + io.netty + netty-codec + ${netty.version} + + + + io.netty + netty-codec-socks + ${netty.version} + + + + io.netty + netty-handler-proxy + ${netty.version} + + + + io.netty + netty-common + ${netty.version} + + + + io.netty + netty-transport + ${netty.version} + + + + io.netty + netty-handler + ${netty.version} + + + + io.netty + netty-resolver-dns + ${netty.version} + + + + io.netty + netty-transport-native-epoll + linux-x86_64 + ${netty.version} + true + + + + io.netty + netty-transport-native-epoll + linux-aarch_64 + ${netty.version} + true + + + + io.netty + netty-transport-native-kqueue + osx-x86_64 + ${netty.version} + true + + + + io.netty + netty-transport-native-kqueue + osx-aarch_64 + ${netty.version} + true + + + + io.netty.incubator + netty-incubator-transport-native-io_uring + ${netty.iouring} + linux-x86_64 + true + + + + io.netty.incubator + netty-incubator-transport-native-io_uring + ${netty.iouring} + linux-aarch_64 + true + + + + com.github.luben + zstd-jni + ${zstd-jni.version} + true + + + + com.aayushatharva.brotli4j + brotli4j + ${brotli4j.version} + true + + + com.aayushatharva.brotli4j + native-linux-x86_64 + ${brotli4j.version} + true + + + com.aayushatharva.brotli4j + native-linux-aarch64 + ${brotli4j.version} + true + + + com.aayushatharva.brotli4j + native-linux-riscv64 + ${brotli4j.version} + true + + + com.aayushatharva.brotli4j + native-osx-x86_64 + ${brotli4j.version} + true + + + com.aayushatharva.brotli4j + native-osx-aarch64 + ${brotli4j.version} + true + + + com.aayushatharva.brotli4j + native-windows-x86_64 + ${brotli4j.version} + true + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + com.sun.activation + jakarta.activation + ${activation.version} + + + org.jetbrains + annotations + ${jetbrains-annotations.version} + + + - - - true - src/main/resources/ - - - - - - org.apache.maven.wagon - wagon-ssh-external - 1.0-beta-6 - - - org.apache.maven.scm - maven-scm-provider-gitexe - 1.6 - - - org.apache.maven.scm - maven-scm-manager-plexus - 1.6 - - - org.kathrynhuxtable.maven.wagon - wagon-gitsite - 0.3.1 - - - install org.apache.maven.plugins maven-compiler-plugin - 2.3.2 + 3.14.0 - ${source.property} - ${target.property} - 1024m - + 11 + 11 + UTF-8 + + -XDcompilePolicy=simple + -Xplugin:ErrorProne + -Xep:JavaTimeDefaultTimeZone:ERROR + -Xep:JavaUtilDate:ERROR + -Xep:DateChecker:ERROR + -Xep:DateFormatConstant:ERROR + -Xep:EmptyBlockTag:ERROR + -Xep:VariableNameSameAsType:ERROR + -Xep:DoubleCheckedLocking:ERROR + -Xep:DefaultCharset:ERROR + -Xep:NullablePrimitive:ERROR + -Xep:NullOptional:ERROR + -XepExcludedPaths:.*/src/test/java/.* + -XepOpt:NullAway:AnnotatedPackages=org.asynchttpclient + -XepOpt:NullAway:UnannotatedSubPackages=org.asynchttpclient.netty,org.asynchttpclient.request + -XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true + -Xep:NullAway:ERROR + + + + + com.google.errorprone + error_prone_core + 2.31.0 + + + com.uber.nullaway + nullaway + 0.12.6 + + + + org.apache.maven.plugins maven-surefire-plugin - ${surefire.version} + 3.5.2 - - ${surefire.redirectTestOutputToFile} - + + @{argLine} --add-exports java.base/jdk.internal.misc=ALL-UNNAMED + + - org.codehaus.mojo - animal-sniffer-maven-plugin - 1.6 - - - org.codehaus.mojo.signature - java16 - 1.0 - - + org.jacoco + jacoco-maven-plugin + 0.8.12 - check-java-1.6-compat - process-classes - check + prepare-agent - - - - org.apache.felix - maven-bundle-plugin - 2.3.4 - true - - META-INF - - - $(replace;$(project.version);-SNAPSHOT;.$(tstamp;yyyyMMdd-HHmm)) - - Sonatype - - - - osgi-bundle - package + report + test - bundle + report + org.apache.maven.plugins - maven-enforcer-plugin - 1.0-beta-1 + maven-source-plugin + 3.2.1 - enforce-versions - enforce + jar-no-fork - - - - 2.0.9 - - - 1.5 - - - + - maven-resources-plugin - 2.4.3 - - UTF-8 - - - - maven-release-plugin - - - maven-jar-plugin - 2.3.1 + org.apache.maven.plugins + maven-javadoc-plugin + 3.11.1 + attach-javadocs - test-jar + jar + - maven-source-plugin - 2.1.2 + org.sonatype.plugins + nexus-staging-maven-plugin + 1.7.0 + true + + ossrh + https://oss.sonatype.org/ + false + false + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.7 - attach-sources + sign-artifacts verify - jar-no-fork + sign + + + + --pinentry-mode + loopback + + false + + - maven-site-plugin - 3.0 - - - maven-javadoc-plugin - 2.8.1 + com.github.siom79.japicmp + japicmp-maven-plugin + 0.23.1 - true - 1.6 - UTF-8 - 1g - - http://java.sun.com/javase/6/docs/api/ - + + RELEASE + ${project.version} + + + true + true + true + false + public + - attach-javadocs - verify - jar + cmp + verify - - - - maven-javadoc-plugin - 2.8.1 - - true - 1.6 - UTF-8 - 1g - - http://java.sun.com/javase/6/docs/api/ - - ${sun.boot.class.path} - com.google.doclava.Doclava - false - -J-Xmx1024m - - com.google.doclava - doclava - 1.0.3 - - - -hdf project.name "${project.name} - ${project.version}" - -d - ${project.reporting.outputDirectory}/apidocs - - - - - default - - javadoc - - - - - - maven-surefire-report-plugin - ${surefire.version} - - - - - - release-sign-artifacts - - - performRelease - true - - - - - - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - - - - offline-testing - - - - maven-surefire-plugin - - standalone - - ${surefire.redirectTestOutputToFile} - - - - - - - - online-testing - - - - maven-surefire-plugin - - standalone, online - - ${surefire.redirectTestOutputToFile} - - - - - - - - test-output - - false - - - - - - sonatype-nexus-staging - Sonatype Release - http://oss.sonatype.org/service/local/staging/deploy/maven2 - - - - sonatype-nexus-snapshots - sonatype-nexus-snapshots - ${distMgmtSnapshotsUrl} - - - github - gitsite:git@github.com/sonatype/async-http-client.git - - - - - maven.java.net - https://maven.java.net/content/repositories/releases - - - - api - providers - extras - site - - - - - ch.qos.logback - logback-classic - ${logback.version} - test - - - log4j - log4j - ${log4j.version} - test - - - org.testng - testng - ${testng.version} - test - - - junit - junit - - - org.beanshell - bsh - - - org.yaml - snakeyaml - - - - - org.eclipse.jetty - jetty-servlet - ${jetty.version} - test - - - org.eclipse.jetty - jetty-servlets - ${jetty.version} - test - - - org.eclipse.jetty - jetty-security - ${jetty.version} - test - - - org.eclipse.jetty - jetty-proxy - ${jetty.version} - test - - - org.eclipse.jetty.websocket - websocket-server - ${jetty.version} - test - - - org.eclipse.jetty.websocket - websocket-servlet - ${jetty.version} - test - - - org.apache.tomcat - coyote - ${tomcat.version} - test - - - org.apache.tomcat - catalina - ${tomcat.version} - test - - - org.apache.tomcat - servlet-api - - - - - commons-io - commons-io - ${commons-io.version} - test - - - commons-fileupload - commons-fileupload - ${commons-fileupload.version} - test - - - com.e-movimento.tinytools - privilegedaccessor - ${privilegedaccessor.version} - test - - - - http://oss.sonatype.org/content/repositories/snapshots - true - 1.6 - 1.6 - 2.16 - 1.0.13 - 1.2.17 - 6.8.7 - 9.0.5.v20130815 - 6.0.29 - 2.4 - 1.3 - 1.2.2 - - diff --git a/providers/grizzly/pom.xml b/providers/grizzly/pom.xml deleted file mode 100644 index 269aea00ba..0000000000 --- a/providers/grizzly/pom.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - org.asynchttpclient - async-http-client-providers-parent - 2.0.0-SNAPSHOT - - 4.0.0 - async-http-client-grizzly-provider - Asynchronous Http Client Grizzly Provider - - The Async Http Client Grizzly Provider. - - - - 2.3.16 - 1.1 - - - - - org.glassfish.grizzly - grizzly-websockets - ${grizzly.version} - - - org.glassfish.grizzly - grizzly-spdy - ${grizzly.version} - - - org.glassfish.grizzly - connection-pool - ${grizzly.version} - - - org.glassfish.grizzly - grizzly-npn-api - ${grizzly.npn.version} - - - org.glassfish.grizzly - grizzly-http-server - ${grizzly.version} - test - - - - - - jvnet-nexus-snapshots - https://maven.java.net/content/repositories/snapshots - - false - - - true - - - - - diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java deleted file mode 100644 index f887b9624e..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.ConnectionPoolKeyStrategy; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Request; -import org.asynchttpclient.uri.UriComponents; -import org.glassfish.grizzly.CompletionHandler; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.Grizzly; -import org.glassfish.grizzly.GrizzlyFuture; -import org.glassfish.grizzly.attributes.Attribute; -import org.glassfish.grizzly.connectionpool.EndpointKey; -import org.glassfish.grizzly.filterchain.FilterChainBuilder; -import org.glassfish.grizzly.impl.FutureImpl; -import org.glassfish.grizzly.utils.Futures; -import org.glassfish.grizzly.utils.IdleTimeoutFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.net.ssl.HostnameVerifier; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; -import org.asynchttpclient.providers.grizzly.filters.SwitchingSSLFilter; - -public class ConnectionManager { - - private final static Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class); - - private static final Attribute DO_NOT_CACHE = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(ConnectionManager.class - .getName()); - private final ConnectionPool connectionPool; - private final GrizzlyAsyncHttpProvider provider; - private final boolean canDestroyPool; - private final Map> endpointKeyMap = new HashMap>(); - private final FilterChainBuilder secureBuilder; - private final FilterChainBuilder nonSecureBuilder; - private final boolean asyncConnect; - - // ------------------------------------------------------------ Constructors - - ConnectionManager(final GrizzlyAsyncHttpProvider provider,// - final ConnectionPool connectionPool,// - final FilterChainBuilder secureBuilder,// - final FilterChainBuilder nonSecureBuilder) { - - this.provider = provider; - final AsyncHttpClientConfig config = provider.getClientConfig(); - if (connectionPool != null) { - this.connectionPool = connectionPool; - canDestroyPool = false; - } else { - this.connectionPool = new ConnectionPool(config.getMaxConnectionsPerHost(),// - config.getMaxConnections(),// - null,// - config.getConnectionTimeout(),// - config.getPooledConnectionIdleTimeout(),// - 2000); - canDestroyPool = true; - } - this.secureBuilder = secureBuilder; - this.nonSecureBuilder = nonSecureBuilder; - AsyncHttpProviderConfig providerConfig = config.getAsyncHttpProviderConfig(); - asyncConnect = providerConfig instanceof GrizzlyAsyncHttpProviderConfig ? GrizzlyAsyncHttpProviderConfig.class.cast(providerConfig) - .isAsyncConnectMode() : false; - } - - // ---------------------------------------------------------- Public Methods - - public void doTrackedConnection(final Request request,// - final GrizzlyResponseFuture requestFuture,// - CompletionHandler completionHandler) throws IOException { - final EndpointKey key = getEndPointKey(request, requestFuture.getProxyServer()); - - final HostnameVerifier verifier = getVerifier(); - final UriComponents uri = request.getURI(); - - if (Utils.isSecure(uri) && verifier != null) { - completionHandler = - SwitchingSSLFilter.wrapWithHostnameVerifierHandler( - completionHandler, verifier, uri.getHost()); - } - - if (asyncConnect) { - connectionPool.take(key, completionHandler); - } else { - IOException ioe = null; - GrizzlyFuture future = connectionPool.take(key); - try { - // No explicit timeout when calling get() here as the Grizzly - // endpoint pool will time it out based on the connect timeout - // setting. - completionHandler.completed(future.get()); - } catch (CancellationException e) { - completionHandler.cancelled(); - } catch (ExecutionException ee) { - final Throwable cause = ee.getCause(); - if (cause instanceof ConnectionPool.MaxCapacityException) { - ioe = (IOException) cause; - } else { - completionHandler.failed(ee.getCause()); - } - } catch (Exception ie) { - completionHandler.failed(ie); - } - if (ioe != null) { - throw ioe; - } - } - } - - public Connection obtainConnection(final Request request, final GrizzlyResponseFuture requestFuture) throws ExecutionException, - InterruptedException, TimeoutException, IOException { - - final Connection c = obtainConnection0(request, requestFuture); - markConnectionAsDoNotCache(c); - return c; - - } - - // --------------------------------------------------Package Private Methods - - static void markConnectionAsDoNotCache(final Connection c) { - DO_NOT_CACHE.set(c, Boolean.TRUE); - } - - static boolean isConnectionCacheable(final Connection c) { - final Boolean canCache = DO_NOT_CACHE.get(c); - return ((canCache != null) ? canCache : false); - } - - // --------------------------------------------------------- Private Methods - - private HostnameVerifier getVerifier() { - return provider.getClientConfig().getHostnameVerifier(); - } - - private EndpointKey getEndPointKey(final Request request, final ProxyServer proxyServer) throws IOException { - final String stringKey = getPoolKey(request, proxyServer); - EndpointKey key = endpointKeyMap.get(stringKey); - if (key == null) { - synchronized (endpointKeyMap) { - key = endpointKeyMap.get(stringKey); - if (key == null) { - SocketAddress address = getRemoteAddress(request, proxyServer); - InetAddress localAddress = request.getLocalAddress(); - InetSocketAddress localSocketAddress = null; - if (localAddress != null) { - localSocketAddress = new InetSocketAddress(localAddress.getHostName(), 0); - } - - ProxyAwareConnectorHandler handler = ProxyAwareConnectorHandler.builder(provider.clientTransport) - .nonSecureFilterChainTemplate(nonSecureBuilder).secureFilterChainTemplate(secureBuilder) - .asyncHttpClientConfig(provider.getClientConfig()).uri(request.getURI()).proxyServer(proxyServer).build(); - EndpointKey localKey = new EndpointKey(stringKey, address, localSocketAddress, handler); - endpointKeyMap.put(stringKey, localKey); - key = localKey; - } - } - } - return key; - } - - private SocketAddress getRemoteAddress(final Request request, final ProxyServer proxyServer) { - final UriComponents requestUri = request.getURI(); - final String host = ((proxyServer != null) ? proxyServer.getHost() : requestUri.getHost()); - final int port = ((proxyServer != null) ? proxyServer.getPort() : requestUri.getPort()); - return new InetSocketAddress(host, getPort(request.getURI(), port)); - } - - private static int getPort(final UriComponents uri, final int p) { - int port = p; - if (port == -1) { - final String protocol = uri.getScheme().toLowerCase(Locale.ENGLISH); - if ("http".equals(protocol) || "ws".equals(protocol)) { - port = 80; - } else if ("https".equals(protocol) || "wss".equals(protocol)) { - port = 443; - } else { - throw new IllegalArgumentException("Unknown protocol: " + protocol); - } - } - return port; - } - - private Connection obtainConnection0(final Request request, final GrizzlyResponseFuture requestFuture) throws ExecutionException, - InterruptedException, TimeoutException, IOException { - - final int cTimeout = provider.getClientConfig().getConnectionTimeout(); - final FutureImpl future = Futures.createSafeFuture(); - final CompletionHandler ch = Futures.toCompletionHandler(future, - createConnectionCompletionHandler(request, requestFuture, null)); - final ProxyServer proxyServer = requestFuture.getProxyServer(); - final SocketAddress address = getRemoteAddress(request, proxyServer); - - ProxyAwareConnectorHandler handler = ProxyAwareConnectorHandler.builder(provider.clientTransport) - .nonSecureFilterChainTemplate(nonSecureBuilder)// - .secureFilterChainTemplate(secureBuilder)// - .asyncHttpClientConfig(provider.getClientConfig())// - .uri(request.getURI())// - .proxyServer(proxyServer)// - .build(); - if (cTimeout > 0) { - handler.connect(address, ch); - return future.get(cTimeout, MILLISECONDS); - } else { - handler.connect(address, ch); - return future.get(); - } - } - - boolean returnConnection(final Connection c) { - final boolean result = (DO_NOT_CACHE.get(c) == null && connectionPool.release(c)); - if (result) { - if (provider.getResolver() != null) { - provider.getResolver().setTimeoutMillis(c, IdleTimeoutFilter.FOREVER); - } - } - return result; - } - - void destroy() { - if (canDestroyPool) { - connectionPool.close(); - } - } - - CompletionHandler createConnectionCompletionHandler(// - final Request request,// - final GrizzlyResponseFuture future,// - final CompletionHandler wrappedHandler) { - - return new CompletionHandler() { - public void cancelled() { - if (wrappedHandler != null) { - wrappedHandler.cancelled(); - } else { - future.cancel(true); - } - } - - public void failed(Throwable throwable) { - if (wrappedHandler != null) { - wrappedHandler.failed(throwable); - } else { - future.abort(throwable); - } - } - - public void completed(Connection connection) { - future.setConnection(connection); - provider.touchConnection(connection, request); - if (wrappedHandler != null) { - //connection.addCloseListener(connectionMonitor); - wrappedHandler.completed(connection); - } - } - - public void updated(Connection result) { - if (wrappedHandler != null) { - wrappedHandler.updated(result); - } - } - }; - } - - private static String getPoolKey(final Request request, ProxyServer proxyServer) { - final ConnectionPoolKeyStrategy keyStrategy = request.getConnectionPoolKeyStrategy(); - return keyStrategy.getKey(request.getURI(), proxyServer); - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionPool.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionPool.java deleted file mode 100644 index 11197930d7..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionPool.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2013 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.providers.grizzly; - -import org.glassfish.grizzly.CompletionHandler; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.EmptyCompletionHandler; -import org.glassfish.grizzly.GrizzlyFuture; -import org.glassfish.grizzly.connectionpool.EndpointKey; -import org.glassfish.grizzly.connectionpool.MultiEndpointPool; -import org.glassfish.grizzly.connectionpool.SingleEndpointPool; -import org.glassfish.grizzly.utils.DelayedExecutor; - -import java.io.IOException; -import java.net.SocketAddress; - -/** - * Extension of standard Grizzly {@link MultiEndpointPool}. - * - * @since 2.0 - * @author The Grizzly Team - */ -public class ConnectionPool extends MultiEndpointPool { - - private final Object lock = new Object(); - - // ------------------------------------------------------------ Constructors - - public ConnectionPool(final int maxConnectionsPerEndpoint,// - final int maxConnectionsTotal,// - final DelayedExecutor delayedExecutor,// - final long connectTimeoutMillis,// - final long keepAliveTimeoutMillis,// - final long keepAliveCheckIntervalMillis) { - super(null,// - maxConnectionsPerEndpoint,// - maxConnectionsTotal,// - delayedExecutor,// - connectTimeoutMillis,// - keepAliveTimeoutMillis,// - keepAliveCheckIntervalMillis,// - -1,// - -1); - } - - // ------------------------------------------ Methods from MultiEndpointPool - - protected SingleEndpointPool obtainSingleEndpointPool(final EndpointKey endpointKey) throws IOException { - SingleEndpointPool sePool = endpointToPoolMap.get(endpointKey); - if (sePool == null) { - synchronized (poolSync) { - checkNotClosed(); - if (isMaxCapacityReached()) { - throw new MaxCapacityException(); - } - sePool = endpointToPoolMap.get(endpointKey); - if (sePool == null) { - sePool = createSingleEndpointPool(endpointKey); - endpointToPoolMap.put(endpointKey, sePool); - } - } - } - - return sePool; - } - - @Override - public GrizzlyFuture take(final EndpointKey endpointKey) { - synchronized (lock) { - final GrizzlyFuture f = super.take(endpointKey); - f.addCompletionHandler(new EmptyCompletionHandler() { - @Override - public void completed(Connection result) { - if (Utils.isSpdyConnection(result)) { - release(result); - } - super.completed(result); - } - }); - return f; - } - } - - @Override - public void take(final EndpointKey endpointKey, final CompletionHandler completionHandler) { - synchronized (lock) { - if (completionHandler == null) { - throw new IllegalStateException("CompletionHandler argument cannot be null."); - } - - super.take(endpointKey, new CompletionHandler() { - @Override - public void cancelled() { - completionHandler.cancelled(); - } - - @Override - public void failed(Throwable throwable) { - completionHandler.failed(throwable); - } - - @Override - public void completed(Connection result) { - release(result); - completionHandler.completed(result); - } - - @Override - public void updated(Connection result) { - completionHandler.updated(result); - } - }); - } - } - - @Override - public boolean release(Connection connection) { - synchronized (lock) { - return super.release(connection); - } - } - - // ---------------------------------------------------------- Nested Classes - - public static final class MaxCapacityException extends IOException { - - public MaxCapacityException() { - super("Maximum pool capacity has been reached"); - } - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java deleted file mode 100644 index 50675690ac..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly; - -import static org.asynchttpclient.AsyncHandler.STATE.ABORT; -import static org.asynchttpclient.AsyncHandler.STATE.UPGRADE; -import static org.asynchttpclient.providers.grizzly.statushandler.StatusHandler.InvocationStatus.CONTINUE; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.MaxRedirectException; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.cookie.CookieDecoder; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.listener.TransferCompletionHandler; -import org.asynchttpclient.providers.grizzly.filters.events.ContinueEvent; -import org.asynchttpclient.providers.grizzly.statushandler.AuthorizationHandler; -import org.asynchttpclient.providers.grizzly.statushandler.ProxyAuthorizationHandler; -import org.asynchttpclient.providers.grizzly.statushandler.RedirectHandler; -import org.asynchttpclient.providers.grizzly.statushandler.StatusHandler; -import org.asynchttpclient.providers.grizzly.websocket.GrizzlyWebSocketAdapter; -import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.websocket.WebSocketUpgradeHandler; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.http.HttpHeader; -import org.glassfish.grizzly.http.HttpResponsePacket; -import org.glassfish.grizzly.http.Method; -import org.glassfish.grizzly.http.ProcessingState; -import org.glassfish.grizzly.http.Protocol; -import org.glassfish.grizzly.http.util.Header; -import org.glassfish.grizzly.http.util.HttpStatus; -import org.glassfish.grizzly.utils.IdleTimeoutFilter; -import org.glassfish.grizzly.websockets.SimpleWebSocket; -import org.glassfish.grizzly.websockets.WebSocketHolder; - -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.http.HttpRequestPacket; - -public final class EventHandler { - - private static final Map HANDLER_MAP = new HashMap(); - - static { - HANDLER_MAP.put(HttpStatus.UNAUTHORIZED_401.getStatusCode(), AuthorizationHandler.INSTANCE); - HANDLER_MAP.put(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407.getStatusCode(), ProxyAuthorizationHandler.INSTANCE); - HANDLER_MAP.put(HttpStatus.MOVED_PERMANENTLY_301.getStatusCode(), RedirectHandler.INSTANCE); - HANDLER_MAP.put(HttpStatus.FOUND_302.getStatusCode(), RedirectHandler.INSTANCE); - HANDLER_MAP.put(HttpStatus.SEE_OTHER_303.getStatusCode(), RedirectHandler.INSTANCE); - HANDLER_MAP.put(HttpStatus.TEMPORARY_REDIRECT_307.getStatusCode(), RedirectHandler.INSTANCE); - HANDLER_MAP.put(HttpStatus.PERMANENT_REDIRECT_308.getStatusCode(), RedirectHandler.INSTANCE); - } - - private final AsyncHttpClientConfig config; - GrizzlyAsyncHttpProvider.Cleanup cleanup; - - // -------------------------------------------------------- Constructors - - EventHandler(final AsyncHttpClientConfig config) { - this.config = config; - } - - // ----------------------------------------------------- Event Callbacks - - public void exceptionOccurred(FilterChainContext ctx, Throwable error) { - - HttpTxContext.get(ctx).abort(error); - } - - public void onHttpContentParsed(HttpContent content, FilterChainContext ctx) { - - final HttpTxContext context = HttpTxContext.get(ctx); - final AsyncHandler handler = context.getHandler(); - if (handler != null && context.getCurrentState() != ABORT) { - try { - context.setCurrentState(handler.onBodyPartReceived(new GrizzlyResponseBodyPart(content, ctx.getConnection()))); - } catch (Exception e) { - handler.onThrowable(e); - } - } - - } - - @SuppressWarnings("UnusedParameters") - public void onHttpHeadersEncoded(HttpHeader httpHeader, FilterChainContext ctx) { - final HttpTxContext context = HttpTxContext.get(ctx); - final AsyncHandler handler = context.getHandler(); - if (handler instanceof TransferCompletionHandler) { - ((TransferCompletionHandler) handler).onHeaderWriteCompleted(); - } - } - - public void onHttpContentEncoded(HttpContent content, FilterChainContext ctx) { - final HttpTxContext context = HttpTxContext.get(ctx); - final AsyncHandler handler = context.getHandler(); - if (handler instanceof TransferCompletionHandler) { - final int written = content.getContent().remaining(); - final long total = context.getTotalBodyWritten().addAndGet(written); - ((TransferCompletionHandler) handler).onContentWriteProgress(written, total, content.getHttpHeader().getContentLength()); - } - } - - public void onInitialLineParsed(HttpHeader httpHeader, FilterChainContext ctx) { - - //super.onInitialLineParsed(httpHeader, ctx); - if (httpHeader.isSkipRemainder()) { - return; - } - final HttpTxContext context = HttpTxContext.get(ctx); - final int status = ((HttpResponsePacket) httpHeader).getStatus(); - if (HttpStatus.CONINTUE_100.statusMatches(status)) { - ctx.notifyUpstream(new ContinueEvent(context)); - return; - } - - StatusHandler statusHandler = context.getStatusHandler(); - context.setStatusHandler(null); - if (statusHandler != null && !statusHandler.handlesStatus(status)) { - context.setStatusHandler(null); - context.setInvocationStatus(CONTINUE); - } - - if (context.getInvocationStatus() == CONTINUE) { - if (HANDLER_MAP.containsKey(status)) { - context.setStatusHandler(HANDLER_MAP.get(status)); - } - if (context.getStatusHandler() instanceof RedirectHandler) { - if (!isRedirectAllowed(context)) { - context.setStatusHandler(null); - } - } - } - if (isRedirectAllowed(context)) { - if (isRedirect(status)) { - if (context.getStatusHandler() == null) { - context.setStatusHandler(RedirectHandler.INSTANCE); - } - context.getRedirectCount().incrementAndGet(); - if (redirectCountExceeded(context)) { - httpHeader.setSkipRemainder(true); - context.abort(new MaxRedirectException()); - } - } else { - if (context.getRedirectCount().get() > 0) { - context.getRedirectCount().set(0); - } - } - } - final GrizzlyResponseStatus responseStatus = - new GrizzlyResponseStatus((HttpResponsePacket) httpHeader, - context.getRequest().getURI(), config); - context.setResponseStatus(responseStatus); - if (context.getStatusHandler() != null) { - return; - } - - if (context.getCurrentState() != ABORT) { - try { - final AsyncHandler handler = context.getHandler(); - if (handler != null) { - context.setCurrentState(handler.onStatusReceived(responseStatus)); - if (context.isWSRequest() && context.getCurrentState() == ABORT) { - httpHeader.setSkipRemainder(true); - try { - context.result(handler.onCompleted()); - context.done(); - } catch (Throwable e) { - context.abort(e); - } - } - } - } catch (Exception e) { - httpHeader.setSkipRemainder(true); - context.abort(e); - } - } - - } - - public void onHttpHeaderError(final HttpHeader httpHeader, final FilterChainContext ctx, final Throwable t) { - - httpHeader.setSkipRemainder(true); - HttpTxContext.get(ctx).abort(t); - } - - public void onHttpContentError(final HttpHeader httpHeader, final FilterChainContext ctx, final Throwable t) { - - httpHeader.setSkipRemainder(true); - HttpTxContext.get(ctx).abort(t); - } - - @SuppressWarnings({ "unchecked" }) - public void onHttpHeadersParsed(HttpHeader httpHeader, FilterChainContext ctx) { - - //super.onHttpHeadersParsed(httpHeader, ctx); - GrizzlyAsyncHttpProvider.LOGGER.debug("RESPONSE: {}", httpHeader); - processKeepAlive(ctx.getConnection(), httpHeader); - final HttpTxContext context = HttpTxContext.get(ctx); - - if (httpHeader.isSkipRemainder()) { - return; - } - - final AsyncHandler handler = context.getHandler(); - final GrizzlyResponseHeaders responseHeaders = new GrizzlyResponseHeaders((HttpResponsePacket) httpHeader); - if (context.getProvider().getClientConfig().hasResponseFilters()) { - final List filters = context.getProvider().getClientConfig().getResponseFilters(); - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(handler).request(context.getRequest()) - .responseHeaders(responseHeaders).responseStatus(context.getResponseStatus()).build(); - try { - for (int i = 0, len = filters.size(); i < len; i++) { - final ResponseFilter f = filters.get(i); - fc = f.filter(fc); - } - } catch (Exception e) { - context.abort(e); - } - if (fc.replayRequest()) { - httpHeader.setSkipRemainder(true); - final Request newRequest = fc.getRequest(); - final AsyncHandler newHandler = fc.getAsyncHandler(); - try { - final ConnectionManager m = context.getProvider().getConnectionManager(); - final Connection c = m.obtainConnection(newRequest, context.getFuture()); - final HttpTxContext newContext = context.copy(); - newContext.setRequest(newRequest); - context.setFuture(null); - context.getProvider().execute(c, newRequest, newHandler, context.getFuture(), newContext); - } catch (Exception e) { - context.abort(e); - } - return; - } - } - if (context.getStatusHandler() != null && context.getInvocationStatus() == CONTINUE) { - final boolean result = context.getStatusHandler().handleStatus(((HttpResponsePacket) httpHeader), context, ctx); - if (!result) { - httpHeader.setSkipRemainder(true); - return; - } - } - if (context.isWSRequest()) { - try { - //in case of DIGEST auth protocol handler is null and just returning here is working - if (context.getProtocolHandler() == null) { - return; - //context.protocolHandler = Version.DRAFT17.createHandler(true); - //context.currentState = AsyncHandler.STATE.UPGRADE; - } - - context.getProtocolHandler().setConnection(ctx.getConnection()); - - final GrizzlyWebSocketAdapter webSocketAdapter = createWebSocketAdapter(context); - context.setWebSocket(webSocketAdapter); - SimpleWebSocket ws = webSocketAdapter.getGrizzlyWebSocket(); - if (context.getCurrentState() == UPGRADE) { - httpHeader.setChunked(false); - ws.onConnect(); - WebSocketHolder.set(ctx.getConnection(), context.getProtocolHandler(), ws); - ((WebSocketUpgradeHandler) context.getHandler()).onSuccess(context.getWebSocket()); - final int wsTimeout = context.getProvider().getClientConfig().getWebSocketTimeout(); - IdleTimeoutFilter.setCustomTimeout(ctx.getConnection(), ((wsTimeout <= 0) ? IdleTimeoutFilter.FOREVER : wsTimeout), - TimeUnit.MILLISECONDS); - context.result(handler.onCompleted()); - } else { - httpHeader.setSkipRemainder(true); - ((WebSocketUpgradeHandler) context.getHandler()).onClose(context.getWebSocket(), 1002, - "WebSocket protocol error: unexpected HTTP response status during handshake."); - context.result(null); - } - } catch (Throwable e) { - httpHeader.setSkipRemainder(true); - context.abort(e); - } - } else { - if (context.getCurrentState() != ABORT) { - try { - context.setCurrentState(handler.onHeadersReceived(responseHeaders)); - } catch (Exception e) { - httpHeader.setSkipRemainder(true); - context.abort(e); - } - } - } - - } - - public boolean onHttpHeaderParsed(final HttpHeader httpHeader, - final Buffer buffer, final FilterChainContext ctx) { - final HttpRequestPacket request = ((HttpResponsePacket) httpHeader).getRequest(); - if (Method.CONNECT.equals(request.getMethod())) { - // finish request/response processing, because Grizzly itself - // treats CONNECT traffic as part of request-response processing - // and we don't want it be treated like that - httpHeader.setExpectContent(false); - } - - return false; - } - - @SuppressWarnings("rawtypes") - public boolean onHttpPacketParsed(HttpHeader httpHeader, FilterChainContext ctx) { - - Utils.removeRequestInFlight(ctx.getConnection()); - - if (cleanup != null) { - cleanup.cleanup(ctx); - } - - if (httpHeader.isSkipRemainder()) { - if (Utils.getRequestInFlightCount(ctx.getConnection()) == 0) { - cleanup(ctx); - } - return false; - } - - final HttpTxContext context = HttpTxContext.get(ctx); - cleanup(ctx); - final AsyncHandler handler = context.getHandler(); - if (handler != null) { - try { - context.result(handler.onCompleted()); - } catch (Throwable e) { - context.abort(e); - } - } else { - context.done(); - } - return false; - } - - // ----------------------------------------------------- Private Methods - - @SuppressWarnings("rawtypes") - private static void processKeepAlive(final Connection c, final HttpHeader header) { - final ProcessingState state = header.getProcessingState(); - final String connectionHeader = header.getHeader(Header.Connection); - if (connectionHeader == null) { - state.setKeepAlive(header.getProtocol() == Protocol.HTTP_1_1); - } else { - if ("close".equals(connectionHeader.toLowerCase(Locale.ENGLISH))) { - ConnectionManager.markConnectionAsDoNotCache(c); - state.setKeepAlive(false); - } else { - state.setKeepAlive(true); - } - } - } - - @SuppressWarnings("rawtypes") - private static GrizzlyWebSocketAdapter createWebSocketAdapter(final HttpTxContext context) { - SimpleWebSocket ws = new SimpleWebSocket(context.getProtocolHandler()); - AsyncHttpProviderConfig config = context.getProvider().getClientConfig().getAsyncHttpProviderConfig(); - boolean bufferFragments = true; - if (config instanceof GrizzlyAsyncHttpProviderConfig) { - bufferFragments = (Boolean) ((GrizzlyAsyncHttpProviderConfig) config) - .getProperty(GrizzlyAsyncHttpProviderConfig.Property.BUFFER_WEBSOCKET_FRAGMENTS); - } - - return new GrizzlyWebSocketAdapter(ws, bufferFragments); - } - - private static boolean isRedirectAllowed(final HttpTxContext ctx) { - return ctx.getRequest().getFollowRedirect() != null? ctx.getRequest().getFollowRedirect().booleanValue() : ctx.isRedirectsAllowed(); - } - - @SuppressWarnings("rawtypes") - private static HttpTxContext cleanup(final FilterChainContext ctx) { - - final Connection c = ctx.getConnection(); - final HttpTxContext context = HttpTxContext.remove(ctx); - if (!Utils.isSpdyConnection(c) && !Utils.isIgnored(c)) { - final ConnectionManager manager = context.getProvider().getConnectionManager(); - //if (!manager.canReturnConnection(c)) { - // context.abort( - // new IOException("Maximum pooled connections exceeded")); - //} else { - if (!manager.returnConnection(c)) { - ctx.getConnection().close(); - } - //} - } - - return context; - - } - - private static boolean redirectCountExceeded(final HttpTxContext context) { - return (context.getRedirectCount().get() > context.getMaxRedirectCount()); - } - - public static boolean isRedirect(final int status) { - - return HttpStatus.MOVED_PERMANENTLY_301.statusMatches(status)// - || HttpStatus.FOUND_302.statusMatches(status)// - || HttpStatus.SEE_OTHER_303.statusMatches(status)// - || HttpStatus.TEMPORARY_REDIRECT_307.statusMatches(status) - || HttpStatus.PERMANENT_REDIRECT_308.statusMatches(status); - } - - // ----------------------------------------------------- Private Methods - - public static Request newRequest(final UriComponents uri, final HttpResponsePacket response, final HttpTxContext ctx, boolean asGet) { - - final RequestBuilder builder = new RequestBuilder(ctx.getRequest()); - if (asGet) { - builder.setMethod(Method.GET.getMethodString()); - } - builder.setUrl(uri.toString()); - - if (!ctx.getProvider().getClientConfig().isRemoveQueryParamOnRedirect()) - builder.addQueryParams(ctx.getRequest().getQueryParams()); - - if (response.getHeader(Header.Cookie) != null) { - for (String cookieStr : response.getHeaders().values(Header.Cookie)) { - Cookie c = CookieDecoder.decode(cookieStr); - if (c != null) { - builder.addOrReplaceCookie(c); - } - } - } - return builder.build(); - } - -} // END AsyncHttpClientEventFilter diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/FeedableBodyGenerator.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/FeedableBodyGenerator.java deleted file mode 100644 index 6514edb61a..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/FeedableBodyGenerator.java +++ /dev/null @@ -1,600 +0,0 @@ -/* - * Copyright (c) 2012-2014 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.providers.grizzly; - -import static java.lang.Boolean.TRUE; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.glassfish.grizzly.utils.Exceptions.makeIOException; - -import org.asynchttpclient.Body; -import org.asynchttpclient.BodyGenerator; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.CompletionHandler; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.OutputSink; -import org.glassfish.grizzly.WriteHandler; -import org.glassfish.grizzly.WriteResult; -import org.glassfish.grizzly.filterchain.FilterChain; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.http.HttpContext; -import org.glassfish.grizzly.http.HttpRequestPacket; -import org.glassfish.grizzly.impl.FutureImpl; -import org.glassfish.grizzly.ssl.SSLBaseFilter; -import org.glassfish.grizzly.ssl.SSLFilter; -import org.glassfish.grizzly.threadpool.Threads; -import org.glassfish.grizzly.utils.Futures; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.concurrent.ExecutionException; - -import static org.glassfish.grizzly.ssl.SSLUtils.getSSLEngine; - -/** - * {@link BodyGenerator} which may return just part of the payload at the time - * handler is requesting it. If it happens - PartialBodyGenerator becomes responsible - * for finishing payload transferring asynchronously. - * - * @author The Grizzly Team - * @since 1.7.0 - */ -public class FeedableBodyGenerator implements BodyGenerator { - /** - * There is no limit on bytes waiting to be written. This configuration - * value should be used with caution as it could lead to out-of-memory - * conditions. - */ - @SuppressWarnings("UnusedDeclaration") - public static final int UNBOUND = -1; - - /** - * Defer to whatever the connection has been configured for max pending bytes. - */ - public static final int DEFAULT = -2; - - private volatile HttpRequestPacket requestPacket; - private volatile FilterChainContext context; - private volatile HttpContent.Builder contentBuilder; - - private final EmptyBody EMPTY_BODY = new EmptyBody(); - - private Feeder feeder; - private int origMaxPendingBytes; - private int configuredMaxPendingBytes = DEFAULT; - private boolean asyncTransferInitiated; - - // ---------------------------------------------- Methods from BodyGenerator - - /** - * {@inheritDoc} - */ - @Override - public Body createBody() throws IOException { - return EMPTY_BODY; - } - - // ---------------------------------------------------------- Public Methods - - /** - * Configured the maximum number of bytes that may be pending to be written - * to the wire. If not explicitly configured, the connection's current - * configuration will be used instead. - *

- * Once all data has been fed, the connection's max pending bytes configuration - * will be restored to its original value. - * - * @param maxPendingBytes maximum number of bytes that may be queued to - * be written to the wire. - * @throws IllegalStateException if called after {@link #initializeAsynchronousTransfer(FilterChainContext, HttpRequestPacket)} - * has been called by the {@link GrizzlyAsyncHttpProvider}. - * @throws IllegalArgumentException if maxPendingBytes is less than zero and is - * not {@link #UNBOUND} or {@link #DEFAULT}. - */ - @SuppressWarnings("UnusedDeclaration") - public synchronized void setMaxPendingBytes(final int maxPendingBytes) { - if (maxPendingBytes < DEFAULT) { - throw new IllegalArgumentException("Invalid maxPendingBytes value: " + maxPendingBytes); - } - if (asyncTransferInitiated) { - throw new IllegalStateException("Unable to set max pending bytes after async data transfer has been initiated."); - } - configuredMaxPendingBytes = maxPendingBytes; - } - - /** - * Add a {@link Feeder} implementation that will be invoked when writing - * without blocking is possible. This method must be set before dispatching - * the request this feeder is associated with. - * - * @param feeder the {@link Feeder} responsible for providing data. - * @throws IllegalStateException if called after {@link #initializeAsynchronousTransfer(FilterChainContext, HttpRequestPacket)} - * has been called by the {@link GrizzlyAsyncHttpProvider}. - * @throws IllegalArgumentException if feeder is null - */ - @SuppressWarnings("UnusedDeclaration") - public synchronized void setFeeder(final Feeder feeder) { - if (asyncTransferInitiated) { - throw new IllegalStateException("Unable to set Feeder after async data transfer has been initiated."); - } - if (feeder == null) { - throw new IllegalArgumentException("Feeder argument cannot be null."); - } - this.feeder = feeder; - } - - // ------------------------------------------------- Package Private Methods - - /** - * Even though this method is public, it's not intended to be called by - * Developers directly. Please avoid doing so. - */ - public synchronized void initializeAsynchronousTransfer(final FilterChainContext context, final HttpRequestPacket requestPacket) - throws IOException { - - if (asyncTransferInitiated) { - throw new IllegalStateException("Async transfer has already been initiated."); - } - if (feeder == null) { - throw new IllegalStateException("No feeder available to perform the transfer."); - } - assert (context != null); - assert (requestPacket != null); - - this.requestPacket = requestPacket; - this.contentBuilder = HttpContent.builder(requestPacket); - final Connection c = context.getConnection(); - origMaxPendingBytes = c.getMaxAsyncWriteQueueSize(); - if (configuredMaxPendingBytes != DEFAULT) { - c.setMaxAsyncWriteQueueSize(configuredMaxPendingBytes); - } - this.context = context; - asyncTransferInitiated = true; - final Runnable r = new Runnable() { - @Override - public void run() { - try { - if (requestPacket.isSecure() && - (getSSLEngine(context.getConnection()) == null)) { - flushOnSSLHandshakeComplete(); - } else { - feeder.flush(); - } - } catch (IOException ioe) { - throwError(ioe); - } - } - }; - - // If the current thread is a selector thread, we need to execute - // the remainder of the task on the worker thread to prevent - // it from being blocked. - if (isServiceThread()) { - c.getTransport().getWorkerThreadPool().execute(r); - } else { - r.run(); - } - } - - // --------------------------------------------------------- Private Methods - - private boolean isServiceThread() { - return Threads.isService(); - } - - - private void flushOnSSLHandshakeComplete() throws IOException { - final FilterChain filterChain = context.getFilterChain(); - final int idx = filterChain.indexOfType(SSLFilter.class); - assert (idx != -1); - final SSLFilter filter = (SSLFilter) filterChain.get(idx); - final Connection c = context.getConnection(); - filter.addHandshakeListener(new SSLBaseFilter.HandshakeListener() { - public void onStart(Connection connection) { - } - - public void onComplete(Connection connection) { - if (c.equals(connection)) { - filter.removeHandshakeListener(this); - try { - feeder.flush(); - } catch (IOException ioe) { - throwError(ioe); - } - } - } - }); - filter.handshake(context.getConnection(), null); - } - - private void throwError(final Throwable t) { - HttpTxContext httpTxContext = HttpTxContext.get(context); - httpTxContext.abort(t); - } - - // ----------------------------------------------------------- Inner Classes - - private final class EmptyBody implements Body { - - @Override - public long getContentLength() { - return -1; - } - - @Override - public long read(final ByteBuffer buffer) throws IOException { - return 0; - } - - @Override - public void close() throws IOException { - context.completeAndRecycle(); - context = null; - requestPacket = null; - contentBuilder = null; - } - - } // END EmptyBody - - // ---------------------------------------------------------- Nested Classes - - /** - * Specifies the functionality all Feeders must implement. Typically, - * developers need not worry about implementing this interface directly. - * It should be sufficient, for most use-cases, to simply use the {@link NonBlockingFeeder} - * or {@link SimpleFeeder} implementations. - */ - public interface Feeder { - - /** - * This method will be invoked when it's possible to begin feeding - * data downstream. Implementations of this method must use {@link #feed(Buffer, boolean)} - * to perform the actual write. - * - * @throws IOException if an I/O error occurs. - */ - void flush() throws IOException; - - /** - * This method will write the specified {@link Buffer} to the connection. - * Be aware that this method may block depending if data is being fed - * faster than it can write. How much data may be queued is dictated - * by {@link #setMaxPendingBytes(int)}. Once this threshold is exceeded, - * the method will block until the write queue length drops below the - * aforementioned threshold. - * - * @param buffer the {@link Buffer} to write. - * @param last flag indicating if this is the last buffer to send. - * @throws IOException if an I/O error occurs. - * @throws java.lang.IllegalArgumentException if buffer - * is null. - * @throws java.lang.IllegalStateException if this method is invoked - * before asynchronous transferring has been initiated. - * @see #setMaxPendingBytes(int) - */ - @SuppressWarnings("UnusedDeclaration") - void feed(final Buffer buffer, final boolean last) throws IOException; - - } // END Feeder - - /** - * Base class for {@link Feeder} implementations. This class provides - * an implementation for the contract defined by the {@link #feed} method. - */ - public static abstract class BaseFeeder implements Feeder { - - protected final FeedableBodyGenerator feedableBodyGenerator; - - // -------------------------------------------------------- Constructors - - protected BaseFeeder(FeedableBodyGenerator feedableBodyGenerator) { - this.feedableBodyGenerator = feedableBodyGenerator; - } - - // --------------------------------------------- Package Private Methods - - /** - * {@inheritDoc} - */ - @SuppressWarnings("UnusedDeclaration") - public final synchronized void feed(final Buffer buffer, final boolean last) throws IOException { - if (buffer == null) { - throw new NullPointerException("buffer"); - } - if (!feedableBodyGenerator.asyncTransferInitiated) { - throw new IllegalStateException("Asynchronous transfer has not been initiated."); - } - blockUntilQueueFree(feedableBodyGenerator.context); - final HttpContent content = feedableBodyGenerator.contentBuilder.content(buffer).last(last).build(); - final CompletionHandler handler = ((last) ? new LastPacketCompletionHandler() : null); - feedableBodyGenerator.context.write(content, handler); - } - - /** - * This method will block if the async write queue is currently larger - * than the configured maximum. The amount of time that this method - * will block is dependent on the write timeout of the transport - * associated with the specified connection. - */ - private static void blockUntilQueueFree(final FilterChainContext ctx) { - HttpContext httpContext = HttpContext.get(ctx); - final OutputSink outputSink = httpContext.getOutputSink(); - if (!outputSink.canWrite()) { - final FutureImpl future = Futures.createSafeFuture(); - outputSink.notifyCanWrite(new WriteHandler() { - - @Override - public void onWritePossible() throws Exception { - future.result(TRUE); - } - - @Override - public void onError(Throwable t) { - future.failure(makeIOException(t)); - } - }); - - block(ctx, future); - } - } - - private static void block(final FilterChainContext ctx, final FutureImpl future) { - try { - final long writeTimeout = ctx.getConnection().getTransport().getWriteTimeout(MILLISECONDS); - if (writeTimeout != -1) { - future.get(writeTimeout, MILLISECONDS); - } else { - future.get(); - } - } catch (ExecutionException e) { - HttpTxContext httpTxContext = HttpTxContext.get(ctx); - httpTxContext.abort(e.getCause()); - } catch (Exception e) { - HttpTxContext httpTxContext = HttpTxContext.get(ctx); - httpTxContext.abort(e); - } - } - - // ------------------------------------------------------- Inner Classes - - private final class LastPacketCompletionHandler implements CompletionHandler { - - private final CompletionHandler delegate; - private final Connection c; - private final int origMaxPendingBytes; - - // -------------------------------------------------------- Constructors - - @SuppressWarnings("unchecked") - private LastPacketCompletionHandler() { - delegate = ((!feedableBodyGenerator.requestPacket.isCommitted()) ? feedableBodyGenerator.context.getTransportContext() - .getCompletionHandler() : null); - c = feedableBodyGenerator.context.getConnection(); - origMaxPendingBytes = feedableBodyGenerator.origMaxPendingBytes; - } - - // -------------------------------------- Methods from CompletionHandler - - @Override - public void cancelled() { - c.setMaxAsyncWriteQueueSize(origMaxPendingBytes); - if (delegate != null) { - delegate.cancelled(); - } - } - - @Override - public void failed(Throwable throwable) { - c.setMaxAsyncWriteQueueSize(origMaxPendingBytes); - if (delegate != null) { - delegate.failed(throwable); - } - - } - - @Override - public void completed(WriteResult result) { - c.setMaxAsyncWriteQueueSize(origMaxPendingBytes); - if (delegate != null) { - delegate.completed(result); - } - - } - - @Override - public void updated(WriteResult result) { - if (delegate != null) { - delegate.updated(result); - } - } - - } // END LastPacketCompletionHandler - - } // END Feeder - - /** - * Implementations of this class provide the framework to read data from - * some source and feed data to the {@link FeedableBodyGenerator} - * without blocking. - */ - @SuppressWarnings("UnusedDeclaration") - public static abstract class NonBlockingFeeder extends BaseFeeder { - - // -------------------------------------------------------- Constructors - - /** - * Constructs the NonBlockingFeeder with the associated - * {@link FeedableBodyGenerator}. - */ - public NonBlockingFeeder(final FeedableBodyGenerator feedableBodyGenerator) { - super(feedableBodyGenerator); - } - - // ------------------------------------------------------ Public Methods - - /** - * Notification that it's possible to send another block of data via - * {@link #feed(org.glassfish.grizzly.Buffer, boolean)}. - *

- * It's important to only invoke {@link #feed(Buffer, boolean)} - * once per invocation of {@link #canFeed()}. - */ - public abstract void canFeed() throws IOException; - - /** - * @return true if all data has been fed by this feeder, - * otherwise returns false. - */ - public abstract boolean isDone(); - - /** - * @return true if data is available to be fed, otherwise - * returns false. When this method returns false, - * the {@link FeedableBodyGenerator} will call {@link #notifyReadyToFeed(ReadyToFeedListener)} - * by which this {@link NonBlockingFeeder} implementation may signal data is once - * again available to be fed. - */ - public abstract boolean isReady(); - - /** - * Callback registration to signal the {@link FeedableBodyGenerator} that - * data is available once again to continue feeding. Once this listener - * has been invoked, the NonBlockingFeeder implementation should no longer maintain - * a reference to the listener. - */ - public abstract void notifyReadyToFeed(final ReadyToFeedListener listener); - - // ------------------------------------------------- Methods from Feeder - - /** - * {@inheritDoc} - */ - @Override - public synchronized void flush() throws IOException { - final HttpContext httpContext = HttpContext.get(feedableBodyGenerator.context); - final OutputSink outputSink = httpContext.getOutputSink(); - if (isReady()) { - final boolean notReady = writeUntilFullOrDone(outputSink); - if (!isDone()) { - if (notReady) { - notifyReadyToFeed(new ReadyToFeedListenerImpl()); - } else { - // write queue is full, leverage WriteListener to let us know - // when it is safe to write again. - outputSink.notifyCanWrite(new WriteHandlerImpl()); - } - } - } else { - notifyReadyToFeed(new ReadyToFeedListenerImpl()); - } - } - - // ----------------------------------------------------- Private Methods - - private boolean writeUntilFullOrDone(final OutputSink outputSink) - throws IOException { - while (outputSink.canWrite()) { - if (isReady()) { - canFeed(); - } else { - return true; - } - } - - return false; - } - - // ------------------------------------------------------- Inner Classes - - /** - * Listener to signal that data is available to be fed. - */ - public interface ReadyToFeedListener { - - /** - * Data is once again ready to be fed. - */ - @SuppressWarnings("UnusedDeclaration") - void ready(); - - } // END ReadyToFeedListener - - private final class WriteHandlerImpl implements WriteHandler { - - private final Connection c; - private final FilterChainContext ctx; - - // -------------------------------------------------------- Constructors - - private WriteHandlerImpl() { - this.c = feedableBodyGenerator.context.getConnection(); - this.ctx = feedableBodyGenerator.context; - } - - // ------------------------------------------ Methods from WriteListener - - @Override - public void onWritePossible() throws Exception { - flush(); - } - - @Override - public void onError(Throwable t) { - if (!Utils.isSpdyConnection(c)) { - c.setMaxAsyncWriteQueueSize(feedableBodyGenerator.origMaxPendingBytes); - } - feedableBodyGenerator.throwError(t); - } - - } // END WriteHandlerImpl - - private final class ReadyToFeedListenerImpl implements NonBlockingFeeder.ReadyToFeedListener { - - // ------------------------------------ Methods from ReadyToFeedListener - - @Override - public void ready() { - try { - flush(); - } catch (IOException e) { - final Connection c = feedableBodyGenerator.context.getConnection(); - if (!Utils.isSpdyConnection(c)) { - c.setMaxAsyncWriteQueueSize(feedableBodyGenerator.origMaxPendingBytes); - } - feedableBodyGenerator.throwError(e); - } - } - - } // END ReadToFeedListenerImpl - - } // END NonBlockingFeeder - - /** - * This simple {@link Feeder} implementation allows the implementation to - * feed data in whatever fashion is deemed appropriate. - */ - @SuppressWarnings("UnusedDeclaration") - public abstract static class SimpleFeeder extends BaseFeeder { - - // -------------------------------------------------------- Constructors - - /** - * Constructs the SimpleFeeder with the associated - * {@link FeedableBodyGenerator}. - */ - public SimpleFeeder(FeedableBodyGenerator feedableBodyGenerator) { - super(feedableBodyGenerator); - } - - } // END SimpleFeeder -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java deleted file mode 100644 index 9215900d30..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java +++ /dev/null @@ -1,539 +0,0 @@ -/* - * Copyright (c) 2012-2014 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.providers.grizzly; - -import static org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProviderConfig.Property.CONNECTION_POOL; -import static org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProviderConfig.Property.MAX_HTTP_PACKET_HEADER_SIZE; -import static org.glassfish.grizzly.asyncqueue.AsyncQueueWriter.AUTO_SIZE; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Request; -import org.asynchttpclient.ntlm.NTLMEngine; -import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProviderConfig.Property; -import org.asynchttpclient.providers.grizzly.filters.AsyncHttpClientEventFilter; -import org.asynchttpclient.providers.grizzly.filters.AsyncHttpClientFilter; -import org.asynchttpclient.providers.grizzly.filters.AsyncSpdyClientEventFilter; -import org.asynchttpclient.providers.grizzly.filters.ClientEncodingFilter; -import org.asynchttpclient.providers.grizzly.filters.SwitchingSSLFilter; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.util.ProxyUtils; -import org.asynchttpclient.util.SslUtils; -import org.glassfish.grizzly.CompletionHandler; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.WriteResult; -import org.glassfish.grizzly.filterchain.Filter; -import org.glassfish.grizzly.filterchain.FilterChain; -import org.glassfish.grizzly.filterchain.FilterChainBuilder; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.filterchain.TransportFilter; -import org.glassfish.grizzly.http.ContentEncoding; -import org.glassfish.grizzly.http.GZipContentEncoding; -import org.glassfish.grizzly.http.HttpClientFilter; -import org.glassfish.grizzly.impl.SafeFutureImpl; -import org.glassfish.grizzly.nio.transport.TCPNIOTransport; -import org.glassfish.grizzly.nio.transport.TCPNIOTransportBuilder; -import org.glassfish.grizzly.npn.ClientSideNegotiator; -import org.glassfish.grizzly.spdy.NextProtoNegSupport; -import org.glassfish.grizzly.spdy.SpdyFramingFilter; -import org.glassfish.grizzly.spdy.SpdyHandlerFilter; -import org.glassfish.grizzly.spdy.SpdyMode; -import org.glassfish.grizzly.spdy.SpdySession; -import org.glassfish.grizzly.ssl.SSLBaseFilter; -import org.glassfish.grizzly.ssl.SSLConnectionContext; -import org.glassfish.grizzly.ssl.SSLEngineConfigurator; -import org.glassfish.grizzly.ssl.SSLFilter; -import org.glassfish.grizzly.ssl.SSLUtils; -import org.glassfish.grizzly.strategies.WorkerThreadIOStrategy; -import org.glassfish.grizzly.utils.DelayedExecutor; -import org.glassfish.grizzly.utils.IdleTimeoutFilter; -import org.glassfish.grizzly.websockets.WebSocketClientFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; - -import java.io.IOException; -import java.util.LinkedHashSet; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import org.glassfish.grizzly.nio.RoundRobinConnectionDistributor; -import org.glassfish.grizzly.spdy.SpdyVersion; -import org.glassfish.grizzly.threadpool.ThreadPoolConfig; - -/** - * A Grizzly 2.0-based implementation of {@link AsyncHttpProvider}. - * - * @author The Grizzly Team - * @since 1.7.0 - */ -@SuppressWarnings("rawtypes") -public class GrizzlyAsyncHttpProvider implements AsyncHttpProvider { - - public static final Logger LOGGER = LoggerFactory.getLogger(GrizzlyAsyncHttpProvider.class); - public final static NTLMEngine NTLM_ENGINE = new NTLMEngine(); - - private final AsyncHttpClientConfig clientConfig; - - private ConnectionManager connectionManager; - private DelayedExecutor.Resolver resolver; - private DelayedExecutor timeoutExecutor; - - final TCPNIOTransport clientTransport; - - // ------------------------------------------------------------ Constructors - - public GrizzlyAsyncHttpProvider(final AsyncHttpClientConfig clientConfig) { - - this.clientConfig = clientConfig; - final TCPNIOTransportBuilder builder = TCPNIOTransportBuilder.newInstance(); - clientTransport = builder.build(); - initializeTransport(clientConfig); - try { - clientTransport.start(); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - } - - // ------------------------------------------ Methods from AsyncHttpProvider - - /** - * {@inheritDoc} - */ - public ListenableFuture execute(final Request request, final AsyncHandler handler) throws IOException { - - if (clientTransport.isStopped()) { - throw new IOException("AsyncHttpClient has been closed."); - } - final ProxyServer proxy = ProxyUtils.getProxyServer(clientConfig, request); - final GrizzlyResponseFuture future = new GrizzlyResponseFuture(this, request, handler, proxy); - future.setDelegate(SafeFutureImpl. create()); - final CompletionHandler connectHandler = new CompletionHandler() { - @Override - public void cancelled() { - future.cancel(true); - } - - @Override - public void failed(final Throwable throwable) { - future.abort(throwable); - } - - @Override - public void completed(final Connection c) { - try { - touchConnection(c, request); - execute(c, request, handler, future, null); - } catch (Exception e) { - failed(e); - } - } - - @Override - public void updated(final Connection c) { - // no-op - } - }; - - connectionManager.doTrackedConnection(request, future, connectHandler); - - return future; - } - - /** - * {@inheritDoc} - */ - public void close() { - - try { - connectionManager.destroy(); - clientTransport.shutdownNow(); - final ExecutorService service = clientConfig.executorService(); - // service may be null due to a custom configuration that - // leverages Grizzly's SameThreadIOStrategy. - if (service != null) { - service.shutdown(); - } - if (timeoutExecutor != null) { - timeoutExecutor.stop(); - final ExecutorService threadPool = timeoutExecutor.getThreadPool(); - if (threadPool != null) { - threadPool.shutdownNow(); - } - } - } catch (IOException ignored) { - } - - } - - // ---------------------------------------------------------- Public Methods - - public AsyncHttpClientConfig getClientConfig() { - return clientConfig; - } - - public ConnectionManager getConnectionManager() { - return connectionManager; - } - - public DelayedExecutor.Resolver getResolver() { - return resolver; - } - - // ------------------------------------------------------- Protected Methods - - @SuppressWarnings({ "unchecked" }) - public ListenableFuture execute(final Connection c, final Request request, final AsyncHandler handler, - final GrizzlyResponseFuture future, final HttpTxContext httpTxContext) { - Utils.addRequestInFlight(c); - final RequestInfoHolder requestInfoHolder = new RequestInfoHolder(this, request, handler, future, httpTxContext); - c.write(requestInfoHolder, createWriteCompletionHandler(future)); - - return future; - } - - void initializeTransport(final AsyncHttpClientConfig clientConfig) { - - final FilterChainBuilder secure = FilterChainBuilder.stateless(); - secure.add(new TransportFilter()); - - final int timeout = clientConfig.getRequestTimeout(); - if (timeout > 0) { - int delay = 500; - //noinspection ConstantConditions - if (timeout < delay) { - delay = timeout - 10; - if (delay <= 0) { - delay = timeout; - } - } - timeoutExecutor = IdleTimeoutFilter.createDefaultIdleDelayedExecutor(delay, TimeUnit.MILLISECONDS); - timeoutExecutor.start(); - final IdleTimeoutFilter.TimeoutResolver timeoutResolver = new IdleTimeoutFilter.TimeoutResolver() { - @Override - public long getTimeout(FilterChainContext ctx) { - final HttpTxContext context = HttpTxContext.get(ctx); - if (context != null) { - if (context.isWSRequest()) { - return clientConfig.getWebSocketTimeout(); - } - int requestTimeout = AsyncHttpProviderUtils.requestTimeout(clientConfig, context.getRequest()); - if (requestTimeout > 0) { - return requestTimeout; - } - } - return IdleTimeoutFilter.FOREVER; - } - }; - final IdleTimeoutFilter timeoutFilter = new IdleTimeoutFilter(timeoutExecutor, timeoutResolver, - new IdleTimeoutFilter.TimeoutHandler() { - public void onTimeout(Connection connection) { - timeout(connection); - } - }); - secure.add(timeoutFilter); - resolver = timeoutFilter.getResolver(); - } - - SSLContext context = clientConfig.getSSLContext(); - if (context == null) { - try { - context = SslUtils.getInstance().getSSLContext(clientConfig.isAcceptAnyCertificate()); - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - final SSLEngineConfigurator configurator = new SSLEngineConfigurator(context, true, false, false); - final SwitchingSSLFilter sslFilter = new SwitchingSSLFilter(configurator); - secure.add(sslFilter); - - GrizzlyAsyncHttpProviderConfig providerConfig = (GrizzlyAsyncHttpProviderConfig) clientConfig.getAsyncHttpProviderConfig(); - - boolean npnEnabled = NextProtoNegSupport.isEnabled(); - boolean spdyEnabled = clientConfig.isSpdyEnabled(); - - if (spdyEnabled) { - // if NPN isn't available, check to see if it has been explicitly - // disabled. If it has, we assume the user knows what they are doing - // and we enable SPDY without NPN - this effectively disables standard - // HTTP/1.1 support. - if (!npnEnabled && providerConfig != null) { - if ((Boolean) providerConfig.getProperty(Property.NPN_ENABLED)) { - // NPN hasn't been disabled, so it's most likely a configuration problem. - // Log a warning and disable spdy support. - LOGGER.warn("Next Protocol Negotiation support is not available. SPDY support has been disabled."); - spdyEnabled = false; - } - } - } - - final AsyncHttpClientEventFilter eventFilter; - final EventHandler handler = new EventHandler(clientConfig); - if (providerConfig != null) { - eventFilter = new AsyncHttpClientEventFilter(handler, (Integer) providerConfig.getProperty(MAX_HTTP_PACKET_HEADER_SIZE)); - } else { - eventFilter = new AsyncHttpClientEventFilter(handler); - } - handler.cleanup = eventFilter; - ContentEncoding[] encodings = eventFilter.getContentEncodings(); - if (encodings.length > 0) { - for (ContentEncoding encoding : encodings) { - eventFilter.removeContentEncoding(encoding); - } - } - if (clientConfig.isCompressionEnabled()) { - eventFilter.addContentEncoding(new GZipContentEncoding(512, 512, new ClientEncodingFilter())); - } - secure.add(eventFilter); - final AsyncHttpClientFilter clientFilter = new AsyncHttpClientFilter(this, clientConfig); - secure.add(clientFilter); - secure.add(new WebSocketClientFilter()); - - clientTransport.getAsyncQueueIO().getWriter().setMaxPendingBytesPerConnection(AUTO_SIZE); - - clientTransport.setNIOChannelDistributor( - new RoundRobinConnectionDistributor(clientTransport, false, false)); - - final int kernelThreadsCount = - clientConfig.getIoThreadMultiplier() * - Runtime.getRuntime().availableProcessors(); - - clientTransport.setSelectorRunnersCount(kernelThreadsCount); - clientTransport.setKernelThreadPoolConfig( - ThreadPoolConfig.defaultConfig() - .setCorePoolSize(kernelThreadsCount) - .setMaxPoolSize(kernelThreadsCount) - .setPoolName("grizzly-ahc-kernel") -// .setPoolName(Utils.discoverTestName("grizzly-ahc-kernel")) // uncomment for tests to track down the leaked threads - ); - - if (providerConfig != null) { - final TransportCustomizer customizer = (TransportCustomizer) providerConfig.getProperty(Property.TRANSPORT_CUSTOMIZER); - if (customizer != null) { - customizer.customize(clientTransport, secure); - } else { - doDefaultTransportConfig(); - } - } else { - doDefaultTransportConfig(); - } - - // FilterChain for the standard HTTP case has been configured, we now - // copy it and modify for SPDY purposes. - if (spdyEnabled) { - FilterChainBuilder spdyFilterChain = createSpdyFilterChain(secure, npnEnabled); - ProtocolNegotiator pn = new ProtocolNegotiator(spdyFilterChain.build()); - NextProtoNegSupport.getInstance().setClientSideNegotiator(clientTransport, pn); - } - - // Install the HTTP filter chain. - //clientTransport.setProcessor(fcb.build()); - FilterChainBuilder nonSecure = FilterChainBuilder.stateless(); - nonSecure.addAll(secure); - int idx = nonSecure.indexOfType(SSLFilter.class); - nonSecure.remove(idx); - final ConnectionPool pool; - if (providerConfig != null) { - pool = (ConnectionPool) providerConfig.getProperty(CONNECTION_POOL); - } else { - pool = null; - } - connectionManager = new ConnectionManager(this, pool, secure, nonSecure); - - } - - // ------------------------------------------------- Package Private Methods - - void touchConnection(final Connection c, final Request request) { - - int requestTimeout = AsyncHttpProviderUtils.requestTimeout(clientConfig, request); - if (requestTimeout > 0) { - if (resolver != null) { - resolver.setTimeoutMillis(c, System.currentTimeMillis() + requestTimeout); - } - } - - } - - // --------------------------------------------------------- Private Methods - - private FilterChainBuilder createSpdyFilterChain(final FilterChainBuilder fcb, final boolean npnEnabled) { - - FilterChainBuilder spdyFcb = FilterChainBuilder.stateless(); - spdyFcb.addAll(fcb); - int idx = spdyFcb.indexOfType(SSLFilter.class); - Filter f = spdyFcb.get(idx); - - // Adjust the SSLFilter to support NPN - if (npnEnabled) { - SSLBaseFilter sslBaseFilter = (SSLBaseFilter) f; - NextProtoNegSupport.getInstance().configure(sslBaseFilter); - } - - // Remove the HTTP Client filter - this will be replaced by the - // SPDY framing and handler filters. - idx = spdyFcb.indexOfType(HttpClientFilter.class); - spdyFcb.set(idx, new SpdyFramingFilter()); - final SpdyMode spdyMode = ((npnEnabled) ? SpdyMode.NPN : SpdyMode.PLAIN); - AsyncSpdyClientEventFilter spdyFilter = new AsyncSpdyClientEventFilter(new EventHandler(clientConfig), spdyMode, - clientConfig.executorService()); - spdyFilter.setInitialWindowSize(clientConfig.getSpdyInitialWindowSize()); - spdyFilter.setMaxConcurrentStreams(clientConfig.getSpdyMaxConcurrentStreams()); - spdyFcb.add(idx + 1, spdyFilter); - - // Remove the WebSocket filter - not currently supported. - idx = spdyFcb.indexOfType(WebSocketClientFilter.class); - spdyFcb.remove(idx); - - return spdyFcb; - } - - private void doDefaultTransportConfig() { - final ExecutorService service = clientConfig.executorService(); - clientTransport.setIOStrategy(WorkerThreadIOStrategy.getInstance()); - if (service != null) { - clientTransport.setWorkerThreadPool(service); - } else { - final int multiplier = clientConfig.getIoThreadMultiplier(); - final int threadCount = multiplier * Runtime.getRuntime().availableProcessors(); - clientTransport.getWorkerThreadPoolConfig().setCorePoolSize(threadCount).setMaxPoolSize(threadCount); - } - } - - private CompletionHandler createWriteCompletionHandler(final GrizzlyResponseFuture future) { - return new CompletionHandler() { - - public void cancelled() { - future.cancel(true); - } - - public void failed(Throwable throwable) { - future.abort(throwable); - } - - public void completed(WriteResult result) { - } - - public void updated(WriteResult result) { - // no-op - } - - }; - } - - void timeout(final Connection c) { - - final String key = HttpTxContext.class.getName(); - HttpTxContext ctx; - if (!Utils.isSpdyConnection(c)) { - ctx = (HttpTxContext) c.getAttributes().getAttribute(key); - if (ctx != null) { - c.getAttributes().removeAttribute(key); - ctx.abort(new TimeoutException("Timeout exceeded")); - } - } else { - throw new IllegalStateException(); - } - - // if (context != null) { - // HttpTxContext.set(c, null); - // context.abort(new TimeoutException("Timeout exceeded")); - // } - - } - - // ---------------------------------------------------------- Nested Classes - - private static final class ProtocolNegotiator implements ClientSideNegotiator { - private static final SpdyVersion[] SUPPORTED_SPDY_VERSIONS = - {SpdyVersion.SPDY_3_1, SpdyVersion.SPDY_3}; - - private static final String HTTP = "HTTP/1.1"; - - private final FilterChain spdyFilterChain; - private final SpdyHandlerFilter spdyHandlerFilter; - - // -------------------------------------------------------- Constructors - - private ProtocolNegotiator(final FilterChain spdyFilterChain) { - this.spdyFilterChain = spdyFilterChain; - int idx = spdyFilterChain.indexOfType(SpdyHandlerFilter.class); - spdyHandlerFilter = (SpdyHandlerFilter) spdyFilterChain.get(idx); - } - - // ----------------------------------- Methods from ClientSideNegotiator - - @Override - public boolean wantNegotiate(SSLEngine engine) { - GrizzlyAsyncHttpProvider.LOGGER.info("ProtocolSelector::wantNegotiate"); - return true; - } - - @Override - public String selectProtocol(SSLEngine engine, LinkedHashSet protocols) { - GrizzlyAsyncHttpProvider.LOGGER.info("ProtocolSelector::selectProtocol: " + protocols); - final Connection connection = NextProtoNegSupport.getConnection(engine); - - // Give preference to SPDY/3.1 or SPDY/3. If not available, check for HTTP as a - // fallback - for (SpdyVersion version : SUPPORTED_SPDY_VERSIONS) { - final String versionDef = version.toString(); - if (protocols.contains(versionDef)) { - GrizzlyAsyncHttpProvider.LOGGER.info("ProtocolSelector::selecting: " + versionDef); - SSLConnectionContext sslCtx = SSLUtils.getSslConnectionContext(connection); - sslCtx.setNewConnectionFilterChain(spdyFilterChain); - final SpdySession spdySession = - version.newSession(connection, false, spdyHandlerFilter); - - spdySession.setLocalStreamWindowSize(spdyHandlerFilter.getInitialWindowSize()); - spdySession.setLocalMaxConcurrentStreams(spdyHandlerFilter.getMaxConcurrentStreams()); - Utils.setSpdyConnection(connection); - SpdySession.bind(connection, spdySession); - - return versionDef; - } - } - - if (protocols.contains(HTTP)) { - GrizzlyAsyncHttpProvider.LOGGER.info("ProtocolSelector::selecting: " + HTTP); - // Use the default HTTP FilterChain. - return HTTP; - } else { - GrizzlyAsyncHttpProvider.LOGGER.info("ProtocolSelector::selecting NONE"); - // no protocol support. Will close the connection when - // onNoDeal is invoked - return ""; - } - } - - @Override - public void onNoDeal(SSLEngine engine) { - GrizzlyAsyncHttpProvider.LOGGER.info("ProtocolSelector::onNoDeal"); - final Connection connection = NextProtoNegSupport.getConnection(engine); - connection.closeSilently(); - } - } - - public static interface Cleanup { - - void cleanup(final FilterChainContext ctx); - - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProviderConfig.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProviderConfig.java deleted file mode 100644 index 662eb6a5dd..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProviderConfig.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.glassfish.grizzly.http.HttpCodecFilter; -import org.glassfish.grizzly.nio.transport.TCPNIOTransport; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * {@link AsyncHttpProviderConfig} implementation that allows customization - * of the Grizzly runtime outside of the scope of what the - * {@link org.asynchttpclient.AsyncHttpClientConfig} offers. - * - * @see Property - * - * @author The Grizzly Team - * @since 1.7.0 - */ -public class GrizzlyAsyncHttpProviderConfig implements AsyncHttpProviderConfig { - - /** - * Grizzly-specific customization properties. Each property describes - * what it's used for, what the default value is (if any), and what - * the expected type the value of the property should be. - */ - public static enum Property { - - /** - * If this property is specified with a custom {@link TransportCustomizer} - * instance, the {@link TCPNIOTransport} instance that is created by - * {@link GrizzlyAsyncHttpProvider} will be passed to the customizer - * bypassing all default configuration of the transport typically performed - * by the provider. The type of the value associated with this property - * must be TransportCustomizer.class. - * - * @see TransportCustomizer - */ - TRANSPORT_CUSTOMIZER(TransportCustomizer.class), - - /** - * Defines the maximum HTTP packet header size. - * - * @since 1.8 - */ - MAX_HTTP_PACKET_HEADER_SIZE(Integer.class, HttpCodecFilter.DEFAULT_MAX_HTTP_PACKET_HEADER_SIZE), - - /** - * By default, Websocket messages that are fragmented will be buffered. Once all - * fragments have been accumulated, the appropriate onMessage() call back will be - * invoked with the complete message. If this functionality is not desired, set - * this property to false. - */ - BUFFER_WEBSOCKET_FRAGMENTS(Boolean.class, true), - - /** - * By disabling NPN support, SPDY will be used over secure or non-secure channels, - * but no negotiation of the protocol via NPN will occur. In short, this means - * that this instance of AHC will only 'speak' SPDY - HTTP is effectively disabled. - */ - NPN_ENABLED(Boolean.class, true), - - /** - * Grizzly specific connection pool. - */ - CONNECTION_POOL(ConnectionPool.class, null); - - final Object defaultValue; - final Class type; - - private Property(final Class type, final Object defaultValue) { - this.type = type; - this.defaultValue = defaultValue; - } - - private Property(final Class type) { - this(type, null); - } - - boolean hasDefaultValue() { - return (defaultValue != null); - } - - } // END PROPERTY - - private final Map attributes = new HashMap(); - - /** - * @return true if the underlying provider should make new connections asynchronously or not. By default - * new connections are made synchronously. - * - * @since 2.0.0 - */ - private boolean asyncConnectMode; - - public boolean isAsyncConnectMode() { - return asyncConnectMode; - } - - public void setAsyncConnectMode(boolean asyncConnectMode) { - this.asyncConnectMode = asyncConnectMode; - } - - // ------------------------------------ Methods from AsyncHttpProviderConfig - - /** - * {@inheritDoc} - * - * @throws IllegalArgumentException if the type of the specified value - * does not match the expected type of the specified {@link Property}. - */ - @SuppressWarnings("unchecked") - @Override - public AsyncHttpProviderConfig addProperty(Property name, Object value) { - if (name == null) { - return this; - } - if (value == null) { - if (name.hasDefaultValue()) { - value = name.defaultValue; - } else { - return this; - } - } else { - if (!name.type.isAssignableFrom(value.getClass())) { - throw new IllegalArgumentException(String.format( - "The value of property [%s] must be of type [%s]. Type of value provided: [%s].", name.name(), - name.type.getName(), value.getClass().getName())); - } - } - attributes.put(name, value); - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public Object getProperty(Property name) { - Object ret = attributes.get(name); - if (ret == null) { - if (name.hasDefaultValue()) { - ret = name.defaultValue; - } - } - return ret; - } - - /** - * {@inheritDoc} - */ - @Override - public Object removeProperty(Property name) { - if (name == null) { - return null; - } - return attributes.remove(name); - } - - /** - * {@inheritDoc} - */ - @Override - public Set> propertiesSet() { - return attributes.entrySet(); - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponse.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponse.java deleted file mode 100644 index a1825f1b59..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponse.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.providers.ResponseBase; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.http.Cookies; -import org.glassfish.grizzly.http.CookiesBuilder.ServerCookiesBuilder; -import org.glassfish.grizzly.http.util.Header; -import org.glassfish.grizzly.memory.Buffers; -import org.glassfish.grizzly.memory.MemoryManager; -import org.glassfish.grizzly.utils.BufferInputStream; -import org.glassfish.grizzly.utils.Charsets; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * {@link org.asynchttpclient.HttpResponseBodyPart} implementation using the Grizzly 2.0 HTTP client - * codec. - * - * @author The Grizzly Team - * @since 1.7.0 - */ -public class GrizzlyResponse extends ResponseBase { - - private Buffer responseBody; - private boolean initialized; - - // ------------------------------------------------------------ Constructors - - public GrizzlyResponse(final HttpResponseStatus status, final HttpResponseHeaders headers, final List bodyParts) { - super(status, headers, bodyParts); - } - - // --------------------------------------------------- Methods from Response - - /** - * {@inheritDoc} - */ - public InputStream getResponseBodyAsStream() throws IOException { - return new BufferInputStream(getResponseBody0()); - } - - /** - * {@inheritDoc} - */ - public String getResponseBodyExcerpt(int maxLength, String charset) throws IOException { - charset = calculateCharset(charset); - final Buffer responseBody = getResponseBody0(); - final int len = Math.min(responseBody.remaining(), maxLength); - final int pos = responseBody.position(); - return responseBody.toStringContent(getCharset(charset), pos, len + pos); - } - - /** - * {@inheritDoc} - */ - public String getResponseBody(String charset) throws IOException { - return getResponseBody0().toStringContent(getCharset(charset)); - } - - /** - * {@inheritDoc} - */ - @Override - public byte[] getResponseBodyAsBytes() throws IOException { - final Buffer responseBody = getResponseBody0(); - final byte[] responseBodyBytes = new byte[responseBody.remaining()]; - final int origPos = responseBody.position(); - responseBody.get(responseBodyBytes); - responseBody.position(origPos); - return responseBodyBytes; - } - - @Override - public ByteBuffer getResponseBodyAsByteBuffer() throws IOException { - return ByteBuffer.wrap(getResponseBodyAsBytes()); - } - - /** - * {@inheritDoc} - */ - public String getResponseBodyExcerpt(int maxLength) throws IOException { - return getResponseBodyExcerpt(maxLength, null); - } - - /** - * {@inheritDoc} - */ - public String getResponseBody() throws IOException { - return getResponseBody(null); - } - - /** - * @return the response body as a Grizzly {@link Buffer}. - */ - @SuppressWarnings("UnusedDeclaration") - public Buffer getResponseBodyAsBuffer() { - return getResponseBody0(); - } - - /** - * {@inheritDoc} - */ - public List buildCookies() { - - List values = headers.getHeaders().get(Header.SetCookie.toString()); - if (isNonEmpty(values)) { - ServerCookiesBuilder builder = new ServerCookiesBuilder(false, true); - for (int i = 0, len = values.size(); i < len; i++) { - builder.parse(values.get(i)); - } - return convertCookies(builder.build()); - - } else { - return Collections.unmodifiableList(Collections. emptyList()); - } - } - - // --------------------------------------------------------- Private Methods - - private List convertCookies(Cookies cookies) { - - final org.glassfish.grizzly.http.Cookie[] grizzlyCookies = cookies.get(); - List convertedCookies = new ArrayList(grizzlyCookies.length); - for (int i = 0, len = grizzlyCookies.length; i < len; i++) { - org.glassfish.grizzly.http.Cookie gCookie = grizzlyCookies[i]; - convertedCookies.add(new Cookie(gCookie.getName(), gCookie.getValue(), gCookie.getValue(), gCookie.getDomain(), gCookie - .getPath(), -1L, gCookie.getMaxAge(), gCookie.isSecure(), gCookie.isHttpOnly())); - } - return Collections.unmodifiableList(convertedCookies); - } - - private Charset getCharset(final String charset) { - - String charsetLocal = charset; - - if (charsetLocal == null) { - String contentType = getContentType(); - if (contentType != null) { - charsetLocal = AsyncHttpProviderUtils.parseCharset(contentType); - } - } - - if (charsetLocal == null) { - charsetLocal = Charsets.DEFAULT_CHARACTER_ENCODING; - } - - return Charsets.lookupCharset(charsetLocal); - } - - private synchronized Buffer getResponseBody0() { - if (!initialized) { - if (isNonEmpty(bodyParts)) { - if (bodyParts.size() == 1) { - responseBody = ((GrizzlyResponseBodyPart) bodyParts.get(0)).getBodyBuffer(); - } else { - final Buffer firstBuffer = ((GrizzlyResponseBodyPart) bodyParts.get(0)).getBodyBuffer(); - final MemoryManager mm = MemoryManager.DEFAULT_MEMORY_MANAGER; - Buffer constructedBodyBuffer = firstBuffer; - for (int i = 1, len = bodyParts.size(); i < len; i++) { - constructedBodyBuffer = Buffers.appendBuffers(mm, constructedBodyBuffer, - ((GrizzlyResponseBodyPart) bodyParts.get(i)).getBodyBuffer()); - } - responseBody = constructedBodyBuffer; - } - } else { - responseBody = Buffers.EMPTY_BUFFER; - } - initialized = true; - } - return responseBody; - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java deleted file mode 100644 index 451ddae21e..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.HttpResponseBodyPart; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.utils.BufferInputStream; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicReference; - -/** - * {@link HttpResponseBodyPart} implementation using the Grizzly 2.0 HTTP client - * codec. - * - * @author The Grizzly Team - * @since 1.7.0 - */ -class GrizzlyResponseBodyPart extends HttpResponseBodyPart { - - private final HttpContent content; - private final Connection connection; - private final AtomicReference contentBytes = new AtomicReference(); - - // ------------------------------------------------------------ Constructors - - public GrizzlyResponseBodyPart(final HttpContent content, final Connection connection) { - this.content = content; - this.connection = connection; - } - - // --------------------------------------- Methods from HttpResponseBodyPart - - /** - * {@inheritDoc} - */ - @Override - public byte[] getBodyPartBytes() { - byte[] bytes = contentBytes.get(); - if (bytes != null) { - return bytes; - } - final Buffer b = content.getContent(); - final int origPos = b.position(); - bytes = new byte[b.remaining()]; - b.get(bytes); - b.position(origPos); - contentBytes.compareAndSet(null, bytes); - return bytes; - } - - @Override - public InputStream readBodyPartBytes() { - return new BufferInputStream(content.getContent()); - } - - @Override - public int length() { - return content.getContent().remaining(); - } - - /** - * {@inheritDoc} - */ - @Override - public int writeTo(OutputStream outputStream) throws IOException { - - final byte[] bytes = getBodyPartBytes(); - outputStream.write(bytes); - return bytes.length; - } - - /** - * {@inheritDoc} - */ - @Override - public ByteBuffer getBodyByteBuffer() { - return content.getContent().toByteBuffer(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isLast() { - return content.isLast(); - } - - /** - * {@inheritDoc} - */ - @Override - public void markUnderlyingConnectionAsToBeClosed() { - ConnectionManager.markConnectionAsDoNotCache(connection); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isUnderlyingConnectionToBeClosed() { - return !ConnectionManager.isConnectionCacheable(connection); - } - - // ----------------------------------------------- Package Protected Methods - - Buffer getBodyBuffer() { - return content.getContent(); - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseFuture.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseFuture.java deleted file mode 100644 index 3a13110f78..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseFuture.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2012-2013 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.providers.grizzly; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Request; -import org.asynchttpclient.listenable.AbstractListenableFuture; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.impl.FutureImpl; - -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * {@link AbstractListenableFuture} implementation adaptation of Grizzly's - * {@link FutureImpl}. - * - * @author The Grizzly Team - * @since 1.7.0 - */ -public class GrizzlyResponseFuture extends AbstractListenableFuture { - - private final AtomicBoolean done = new AtomicBoolean(false); - private final AtomicBoolean cancelled = new AtomicBoolean(false); - private final AsyncHandler handler; - private final GrizzlyAsyncHttpProvider provider; - private final Request request; - private final ProxyServer proxyServer; - private Connection connection; - - FutureImpl delegate; - - // ------------------------------------------------------------ Constructors - - public GrizzlyResponseFuture(final GrizzlyAsyncHttpProvider provider, final Request request, final AsyncHandler handler, - final ProxyServer proxyServer) { - - this.provider = provider; - this.request = request; - this.handler = handler; - this.proxyServer = proxyServer; - } - - // ----------------------------------- Methods from AbstractListenableFuture - - public void done() { - - if (!done.compareAndSet(false, true) || cancelled.get()) { - return; - } - runListeners(); - } - - public void abort(Throwable t) { - - if (done.get() || !cancelled.compareAndSet(false, true)) { - return; - } - delegate.failure(t); - if (handler != null) { - try { - handler.onThrowable(t); - } catch (Throwable ignore) { - } - } - closeConnection(); - runListeners(); - } - - public void touch() { - provider.touchConnection(connection, request); - } - - public boolean getAndSetWriteHeaders(boolean writeHeaders) { - - // TODO This doesn't currently do anything - and may not make sense - // with our implementation. Needs further analysis. - return writeHeaders; - } - - public boolean getAndSetWriteBody(boolean writeBody) { - - // TODO This doesn't currently do anything - and may not make sense - // with our implementation. Needs further analysis. - return writeBody; - } - - // ----------------------------------------------------- Methods from Future - - public boolean cancel(boolean mayInterruptIfRunning) { - - if (done.get() || !cancelled.compareAndSet(false, true)) { - return false; - } - if (handler != null) { - try { - handler.onThrowable(new CancellationException()); - } catch (Throwable ignore) { - } - } - runListeners(); - return delegate.cancel(mayInterruptIfRunning); - } - - public boolean isCancelled() { - return delegate.isCancelled(); - } - - public boolean isDone() { - return delegate.isDone(); - } - - public V get() throws InterruptedException, ExecutionException { - return delegate.get(); - } - - public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - - if (!delegate.isCancelled() || !delegate.isDone()) { - return delegate.get(timeout, unit); - } else { - return null; - } - } - - // ------------------------------------------------- Package Private Methods - - void setConnection(final Connection connection) { - this.connection = connection; - } - - public void setDelegate(final FutureImpl delegate) { - this.delegate = delegate; - } - - // --------------------------------------------------------- Private Methods - - private void closeConnection() { - - if (connection != null && connection.isOpen()) { - connection.close().recycle(true); - } - } - - public ProxyServer getProxyServer() { - return proxyServer; - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseHeaders.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseHeaders.java deleted file mode 100644 index 6e0893b16b..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseHeaders.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseHeaders; -import org.glassfish.grizzly.http.HttpResponsePacket; -import org.glassfish.grizzly.http.util.MimeHeaders; - -/** - * {@link HttpResponseHeaders} implementation using the Grizzly 2.0 HTTP client - * codec. - * - * @author The Grizzly Team - * @since 1.7.0 - */ -class GrizzlyResponseHeaders extends HttpResponseHeaders { - - private FluentCaseInsensitiveStringsMap headers; - private MimeHeaders grizzlyHeaders; - - // ------------------------------------------------------------ Constructors - - public GrizzlyResponseHeaders(final HttpResponsePacket response) { - - grizzlyHeaders = new MimeHeaders(); - grizzlyHeaders.copyFrom(response.getHeaders()); - } - - // ---------------------------------------- Methods from HttpResponseHeaders - - /** - * {@inheritDoc} - */ - @Override - public synchronized FluentCaseInsensitiveStringsMap getHeaders() { - if (headers == null) { - headers = new FluentCaseInsensitiveStringsMap(); - for (String name : grizzlyHeaders.names()) { - for (String header : grizzlyHeaders.values(name)) { - headers.add(name, header); - } - } - } - return headers; - } - - @Override - public String toString() { - return getHeaders().toString(); - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseStatus.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseStatus.java deleted file mode 100644 index 328a75735e..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseStatus.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2012-2014 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; -import org.asynchttpclient.uri.UriComponents; -import org.glassfish.grizzly.http.HttpResponsePacket; - -import java.util.List; - -/** - * {@link HttpResponseStatus} implementation using the Grizzly 2.0 HTTP client - * codec. - * - * @author The Grizzly Team - * @since 1.7.0 - */ -public class GrizzlyResponseStatus extends HttpResponseStatus { - - private static final String PROTOCOL_NAME = "HTTP"; - private final int statusCode; - private final String statusText; - private final int majorVersion; - private final int minorVersion; - private final String protocolText; - private final HttpResponsePacket response; - - // ------------------------------------------------------------ Constructors - - public GrizzlyResponseStatus(final HttpResponsePacket response, final UriComponents uri, AsyncHttpClientConfig config) { - - super(uri, config); - statusCode = response.getStatus(); - statusText = response.getReasonPhrase(); - majorVersion = response.getProtocol().getMajorVersion(); - minorVersion = response.getProtocol().getMinorVersion(); - protocolText = response.getProtocolString(); - - this.response = response; - } - - // ----------------------------------------- Methods from HttpResponseStatus - - @Override - public Response prepareResponse(HttpResponseHeaders headers, List bodyParts) { - return new GrizzlyResponse(this, headers, bodyParts); - } - - /** - * {@inheritDoc} - */ - @Override - public int getStatusCode() { - return statusCode; - } - - /** - * {@inheritDoc} - */ - @Override - public String getStatusText() { - return statusText; - } - - /** - * {@inheritDoc} - */ - @Override - public String getProtocolName() { - return PROTOCOL_NAME; - } - - /** - * {@inheritDoc} - */ - @Override - public int getProtocolMajorVersion() { - return majorVersion; - } - - /** - * {@inheritDoc} - */ - @Override - public int getProtocolMinorVersion() { - return minorVersion; - } - - /** - * {@inheritDoc} - */ - @Override - public String getProtocolText() { - return protocolText; - } - - /** - * @return internal Grizzly {@link HttpResponsePacket} - */ - public HttpResponsePacket getResponse() { - return response; - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/HttpTxContext.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/HttpTxContext.java deleted file mode 100644 index a69dad744b..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/HttpTxContext.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.Request; -import org.asynchttpclient.providers.grizzly.bodyhandler.BodyHandler; -import org.asynchttpclient.providers.grizzly.statushandler.StatusHandler; -import org.asynchttpclient.providers.grizzly.statushandler.StatusHandler.InvocationStatus; -import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.websocket.WebSocket; -import org.glassfish.grizzly.CloseListener; -import org.glassfish.grizzly.CloseType; -import org.glassfish.grizzly.Closeable; -import org.glassfish.grizzly.Grizzly; -import org.glassfish.grizzly.attributes.Attribute; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpContext; -import org.glassfish.grizzly.websockets.HandShake; -import org.glassfish.grizzly.websockets.ProtocolHandler; - -import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -import org.asynchttpclient.providers.grizzly.filters.events.GracefulCloseEvent; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.filterchain.FilterChain; -import org.glassfish.grizzly.http.HttpResponsePacket; - -public final class HttpTxContext { - - private static final Attribute REQUEST_STATE_ATTR = Grizzly.DEFAULT_ATTRIBUTE_BUILDER - .createAttribute(HttpTxContext.class.getName()); - - private final AtomicInteger redirectCount = new AtomicInteger(0); - - private final int maxRedirectCount; - private final boolean redirectsAllowed; - private final GrizzlyAsyncHttpProvider provider; - - private Request request; - private UriComponents requestUri; - private final AsyncHandler handler; - private BodyHandler bodyHandler; - private StatusHandler statusHandler; - private InvocationStatus invocationStatus = InvocationStatus.CONTINUE; - private GrizzlyResponseStatus responseStatus; - private GrizzlyResponseFuture future; - private String lastRedirectURI; - private final AtomicLong totalBodyWritten = new AtomicLong(); - private AsyncHandler.STATE currentState; - - private UriComponents wsRequestURI; - private boolean isWSRequest; - private HandShake handshake; - private ProtocolHandler protocolHandler; - private WebSocket webSocket; - private final CloseListener listener = new CloseListener() { - @Override - public void onClosed(Closeable closeable, CloseType type) throws IOException { - if (responseStatus != null && // responseStatus==null if request wasn't even sent - isGracefullyFinishResponseOnClose()) { - // Connection was closed. - // This event is fired only for responses, which don't have - // associated transfer-encoding or content-length. - // We have to complete such a request-response processing gracefully. - final Connection c = responseStatus.getResponse() - .getRequest().getConnection(); - final FilterChain fc = (FilterChain) c.getProcessor(); - - fc.fireEventUpstream(c, - new GracefulCloseEvent(HttpTxContext.this), null); - } else if (CloseType.REMOTELY.equals(type)) { - abort(AsyncHttpProviderUtils.REMOTELY_CLOSED_EXCEPTION); - } - } - }; - - // -------------------------------------------------------- Constructors - - private HttpTxContext(final GrizzlyAsyncHttpProvider provider, final GrizzlyResponseFuture future, final Request request, - final AsyncHandler handler) { - this.provider = provider; - this.future = future; - this.request = request; - this.handler = handler; - redirectsAllowed = this.provider.getClientConfig().isFollowRedirect(); - maxRedirectCount = this.provider.getClientConfig().getMaxRedirects(); - this.requestUri = request.getURI(); - } - - // ---------------------------------------------------------- Public Methods - - public static void set(final FilterChainContext ctx, final HttpTxContext httpTxContext) { - HttpContext httpContext = HttpContext.get(ctx); - httpContext.getCloseable().addCloseListener(httpTxContext.listener); - REQUEST_STATE_ATTR.set(httpContext, httpTxContext); - } - - public static HttpTxContext remove(final FilterChainContext ctx) { - final HttpContext httpContext = HttpContext.get(ctx); - final HttpTxContext httpTxContext = REQUEST_STATE_ATTR.remove(httpContext); - if (httpTxContext != null) { - httpContext.getCloseable().removeCloseListener(httpTxContext.listener); - } - - return httpTxContext; - } - - public static HttpTxContext get(FilterChainContext ctx) { - HttpContext httpContext = HttpContext.get(ctx); - return ((httpContext != null) ? REQUEST_STATE_ATTR.get(httpContext) : null); - } - - public static HttpTxContext create(final RequestInfoHolder requestInfoHolder) { - return new HttpTxContext(requestInfoHolder.getProvider(),// - requestInfoHolder.getFuture(),// - requestInfoHolder.getRequest(),// - requestInfoHolder.getHandler()); - } - - public void abort(final Throwable t) { - if (future != null) { - future.abort(t); - } - } - - public AtomicInteger getRedirectCount() { - return redirectCount; - } - - public int getMaxRedirectCount() { - return maxRedirectCount; - } - - public boolean isRedirectsAllowed() { - return redirectsAllowed; - } - - public GrizzlyAsyncHttpProvider getProvider() { - return provider; - } - - public Request getRequest() { - return request; - } - - public void setRequest(Request request) { - this.request = request; - } - - public UriComponents getRequestUri() { - return requestUri; - } - - public void setRequestUri(UriComponents requestUri) { - this.requestUri = requestUri; - } - - public AsyncHandler getHandler() { - return handler; - } - - public BodyHandler getBodyHandler() { - return bodyHandler; - } - - public void setBodyHandler(BodyHandler bodyHandler) { - this.bodyHandler = bodyHandler; - } - - public StatusHandler getStatusHandler() { - return statusHandler; - } - - public void setStatusHandler(StatusHandler statusHandler) { - this.statusHandler = statusHandler; - } - - public InvocationStatus getInvocationStatus() { - return invocationStatus; - } - - public void setInvocationStatus(InvocationStatus invocationStatus) { - this.invocationStatus = invocationStatus; - } - - public GrizzlyResponseStatus getResponseStatus() { - return responseStatus; - } - - public void setResponseStatus(GrizzlyResponseStatus responseStatus) { - this.responseStatus = responseStatus; - } - - public GrizzlyResponseFuture getFuture() { - return future; - } - - public void setFuture(GrizzlyResponseFuture future) { - this.future = future; - } - - public String getLastRedirectURI() { - return lastRedirectURI; - } - - public void setLastRedirectURI(String lastRedirectURI) { - this.lastRedirectURI = lastRedirectURI; - } - - public AtomicLong getTotalBodyWritten() { - return totalBodyWritten; - } - - public AsyncHandler.STATE getCurrentState() { - return currentState; - } - - public void setCurrentState(AsyncHandler.STATE currentState) { - this.currentState = currentState; - } - - public UriComponents getWsRequestURI() { - return wsRequestURI; - } - - public void setWsRequestURI(UriComponents wsRequestURI) { - this.wsRequestURI = wsRequestURI; - } - - public boolean isWSRequest() { - return isWSRequest; - } - - public void setWSRequest(boolean WSRequest) { - isWSRequest = WSRequest; - } - - public HandShake getHandshake() { - return handshake; - } - - public void setHandshake(HandShake handshake) { - this.handshake = handshake; - } - - public ProtocolHandler getProtocolHandler() { - return protocolHandler; - } - - public void setProtocolHandler(ProtocolHandler protocolHandler) { - this.protocolHandler = protocolHandler; - } - - public WebSocket getWebSocket() { - return webSocket; - } - - public void setWebSocket(WebSocket webSocket) { - this.webSocket = webSocket; - } - - private boolean isGracefullyFinishResponseOnClose() { - final HttpResponsePacket response = responseStatus.getResponse(); - return !response.getProcessingState().isKeepAlive() && - !response.isChunked() && response.getContentLength() == -1; - } - - // ------------------------------------------------- Package Private Methods - - public HttpTxContext copy() { - final HttpTxContext newContext = new HttpTxContext(provider, future, request, handler); - newContext.invocationStatus = invocationStatus; - newContext.bodyHandler = bodyHandler; - newContext.currentState = currentState; - newContext.statusHandler = statusHandler; - newContext.lastRedirectURI = lastRedirectURI; - newContext.redirectCount.set(redirectCount.get()); - return newContext; - } - - void done() { - if (future != null) { - future.done(); - } - } - - @SuppressWarnings({ "unchecked" }) - void result(Object result) { - if (future != null) { - future.delegate.result(result); - future.done(); - } - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ProxyAwareConnectorHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ProxyAwareConnectorHandler.java deleted file mode 100644 index adff9edb41..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ProxyAwareConnectorHandler.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2013 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.providers.grizzly.filters.ProxyFilter; -import org.asynchttpclient.providers.grizzly.filters.TunnelFilter; -import org.asynchttpclient.uri.UriComponents; -import org.glassfish.grizzly.Processor; -import org.glassfish.grizzly.filterchain.FilterChain; -import org.glassfish.grizzly.filterchain.FilterChainBuilder; -import org.glassfish.grizzly.http.HttpClientFilter; -import org.glassfish.grizzly.nio.transport.TCPNIOConnectorHandler; -import org.glassfish.grizzly.nio.transport.TCPNIOTransport; - -final class ProxyAwareConnectorHandler extends TCPNIOConnectorHandler { - - private FilterChainBuilder nonSecureTemplate; - private FilterChainBuilder secureTemplate; - private AsyncHttpClientConfig clientConfig; - private UriComponents uri; - private ProxyServer proxyServer; - - // ------------------------------------------------------------ Constructors - - private ProxyAwareConnectorHandler(final TCPNIOTransport transport) { - super(transport); - } - - // ---------------------------------------------------------- Public Methods - - public static Builder builder(final TCPNIOTransport transport) { - return new ProxyAwareConnectorHandler.Builder(transport); - } - - // ------------------------------------------- Methods from ConnectorHandler - - @Override - public Processor getProcessor() { - return ((proxyServer != null) ? createProxyFilterChain() : createFilterChain()); - } - - // --------------------------------------------------------- Private Methods - - private FilterChain createFilterChain() { - return Utils.isSecure(uri) ? secureTemplate.build() : nonSecureTemplate.build(); - } - - private FilterChain createProxyFilterChain() { - final FilterChainBuilder builder = FilterChainBuilder.stateless(); - if (Utils.isSecure(uri)) { - builder.addAll(secureTemplate); - updateSecureFilterChain(builder); - } else { - builder.addAll(nonSecureTemplate); - updateNonSecureFilterChain(builder); - } - return builder.build(); - } - - private void updateSecureFilterChain(final FilterChainBuilder builder) { - builder.add(1, new TunnelFilter(proxyServer, uri)); - final int idx = builder.indexOfType(HttpClientFilter.class); - assert (idx != -1); - builder.add(idx + 1, new ProxyFilter(proxyServer, clientConfig, true)); - } - - private void updateNonSecureFilterChain(final FilterChainBuilder builder) { - final int idx = builder.indexOfType(HttpClientFilter.class); - assert (idx != -1); - builder.add(idx + 1, new ProxyFilter(proxyServer, clientConfig, false)); - } - - // ---------------------------------------------------------- Nested Classes - - public static final class Builder extends TCPNIOConnectorHandler.Builder { - - final ProxyAwareConnectorHandler connectorHandler; - - // -------------------------------------------------------- Constructors - - private Builder(final TCPNIOTransport transport) { - connectorHandler = new ProxyAwareConnectorHandler(transport); - } - - // ----------------------------------------------------- Builder Methods - - public Builder secureFilterChainTemplate(final FilterChainBuilder secureTemplate) { - connectorHandler.secureTemplate = secureTemplate; - return this; - } - - public Builder nonSecureFilterChainTemplate(final FilterChainBuilder nonSecureTemplate) { - connectorHandler.nonSecureTemplate = nonSecureTemplate; - return this; - } - - public Builder asyncHttpClientConfig(final AsyncHttpClientConfig clientConfig) { - connectorHandler.clientConfig = clientConfig; - return this; - } - - public Builder uri(final UriComponents uri) { - connectorHandler.uri = uri; - return this; - } - - public Builder proxyServer(final ProxyServer proxyServer) { - connectorHandler.proxyServer = proxyServer; - return this; - } - - @Override - public ProxyAwareConnectorHandler build() { - assert (connectorHandler.secureTemplate != null); - assert (connectorHandler.nonSecureTemplate != null); - assert (connectorHandler.clientConfig != null); - assert (connectorHandler.uri != null); - return connectorHandler; - } - } // END Builder -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/RequestInfoHolder.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/RequestInfoHolder.java deleted file mode 100644 index 94dfbdbd48..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/RequestInfoHolder.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2013 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.providers.grizzly; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.Request; - -public class RequestInfoHolder { - - private final GrizzlyAsyncHttpProvider provider; - private final Request request; - private final AsyncHandler handler; - private final GrizzlyResponseFuture future; - private final HttpTxContext httpTxContext; - - // ------------------------------------------------------------ Constructors - - public RequestInfoHolder(final GrizzlyAsyncHttpProvider provider, final Request request, final AsyncHandler handler, - final GrizzlyResponseFuture future, final HttpTxContext httpTxContext) { - this.provider = provider; - this.request = request; - this.handler = handler; - this.future = future; - this.httpTxContext = httpTxContext; - } - - // ---------------------------------------------------------- Public Methods - - public GrizzlyAsyncHttpProvider getProvider() { - return provider; - } - - public Request getRequest() { - return request; - } - - public AsyncHandler getHandler() { - return handler; - } - - public GrizzlyResponseFuture getFuture() { - return future; - } - - public HttpTxContext getHttpTxContext() { - return httpTxContext; - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/TransportCustomizer.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/TransportCustomizer.java deleted file mode 100644 index 34d5fe43f2..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/TransportCustomizer.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.glassfish.grizzly.filterchain.FilterChainBuilder; -import org.glassfish.grizzly.nio.transport.TCPNIOTransport; - -/** - * This class may be provided as an option to the {@link GrizzlyAsyncHttpProviderConfig} - * and allows low-level customization of the {@link TCPNIOTransport} beyond the - * defaults typically used. - * - * @author The Grizzly Team - * @since 1.7.0 - */ -public interface TransportCustomizer { - - /** - * Customizes the configuration of the provided {@link TCPNIOTransport} - * and {@link FilterChainBuilder} instances. - * - * @param transport the {@link TCPNIOTransport} instance for this client. - * @param filterChainBuilder the {@link FilterChainBuilder} that will - * produce the {@link org.glassfish.grizzly.filterchain.FilterChain} that - * will be used to send/receive data. The FilterChain will be populated - * with the Filters typically used for processing HTTP client requests. - * These filters should generally be left alone. But this does allow - * adding additional filters to the chain to add additional features. - */ - void customize(final TCPNIOTransport transport, final FilterChainBuilder filterChainBuilder); -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/Utils.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/Utils.java deleted file mode 100644 index 8b7ae72a1a..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/Utils.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly; - -import org.asynchttpclient.uri.UriComponents; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.Grizzly; -import org.glassfish.grizzly.attributes.Attribute; -import org.glassfish.grizzly.attributes.AttributeStorage; - -import java.util.concurrent.atomic.AtomicInteger; - -public final class Utils { - - private static final Attribute IGNORE = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(Utils.class.getName() + "-IGNORE"); - private static final Attribute REQUEST_IN_FLIGHT = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(Utils.class - .getName() + "-IN-FLIGHT"); - private static final Attribute SPDY = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(Utils.class.getName() - + "-SPDY-CONNECTION"); - - // ------------------------------------------------------------ Constructors - - private Utils() { - } - - // ---------------------------------------------------------- Public Methods - - public static boolean isSecure(final UriComponents uri) { - final String scheme = uri.getScheme(); - return ("https".equals(scheme) || "wss".equals(scheme)); - } - - public static void connectionIgnored(final Connection c, boolean ignored) { - if (ignored) { - IGNORE.set(c, true); - } else { - IGNORE.remove(c); - } - } - - public static boolean isIgnored(final Connection c) { - Boolean result = IGNORE.get(c); - return (result != null && result); - } - - public static void addRequestInFlight(final AttributeStorage storage) { - AtomicInteger counter = REQUEST_IN_FLIGHT.get(storage); - if (counter == null) { - counter = new AtomicInteger(1); - REQUEST_IN_FLIGHT.set(storage, counter); - } else { - counter.incrementAndGet(); - } - } - - public static void removeRequestInFlight(final AttributeStorage storage) { - AtomicInteger counter = REQUEST_IN_FLIGHT.get(storage); - if (counter != null) { - counter.decrementAndGet(); - } - } - - public static int getRequestInFlightCount(final AttributeStorage storage) { - AtomicInteger counter = REQUEST_IN_FLIGHT.get(storage); - return counter != null ? counter.get() : 0; - } - - public static void setSpdyConnection(final Connection c) { - SPDY.set(c, Boolean.TRUE); - } - - public static boolean isSpdyConnection(final Connection c) { - Boolean result = SPDY.get(c); - return result != null ? result : false; - } - - static String discoverTestName(final String defaultName) { - final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); - final int strackTraceLen = stackTrace.length; - - if (stackTrace[strackTraceLen - 1].getClassName().contains("surefire")) { - for (int i = strackTraceLen - 2; i >= 0; i--) { - if (stackTrace[i].getClassName().contains("org.asynchttpclient.async")) { - return "grizzly-kernel-" + - stackTrace[i].getClassName() + "." + stackTrace[i].getMethodName(); - } - } - } - - return defaultName; - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyGeneratorBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyGeneratorBodyHandler.java deleted file mode 100644 index 53770d3b41..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyGeneratorBodyHandler.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.bodyhandler; - -import org.asynchttpclient.Body; -import org.asynchttpclient.BodyGenerator; -import org.asynchttpclient.Request; -import org.asynchttpclient.providers.grizzly.FeedableBodyGenerator; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.http.HttpRequestPacket; -import org.glassfish.grizzly.memory.Buffers; -import org.glassfish.grizzly.memory.MemoryManager; - -import java.io.IOException; - -public final class BodyGeneratorBodyHandler extends BodyHandler { - - // -------------------------------------------- Methods from BodyHandler - - public boolean handlesBodyType(final Request request) { - return (request.getBodyGenerator() != null); - } - - @SuppressWarnings({ "unchecked" }) - public boolean doHandle(final FilterChainContext ctx, final Request request, final HttpRequestPacket requestPacket) throws IOException { - - final BodyGenerator generator = request.getBodyGenerator(); - final Body bodyLocal = generator.createBody(); - final long len = bodyLocal.getContentLength(); - if (len >= 0) { - requestPacket.setContentLengthLong(len); - } else { - requestPacket.setChunked(true); - } - - final MemoryManager mm = ctx.getMemoryManager(); - boolean last = false; - - while (!last) { - Buffer buffer = mm.allocate(MAX_CHUNK_SIZE); - buffer.allowBufferDispose(true); - - final long readBytes = bodyLocal.read(buffer.toByteBuffer()); - if (readBytes > 0) { - buffer.position((int) readBytes); - buffer.trim(); - } else { - buffer.dispose(); - - if (readBytes < 0) { - last = true; - buffer = Buffers.EMPTY_BUFFER; - } else { - // pass the context to bodyLocal to be able to - // continue body transferring once more data is available - if (generator instanceof FeedableBodyGenerator) { - ((FeedableBodyGenerator) generator).initializeAsynchronousTransfer(ctx, requestPacket); - return false; - } else { - throw new IllegalStateException("BodyGenerator unexpectedly returned 0 bytes available"); - } - } - } - - final HttpContent content = requestPacket.httpContentBuilder().content(buffer).last(last).build(); - ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null)); - } - - return true; - } - -} // END BodyGeneratorBodyHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandler.java deleted file mode 100644 index 2b6e17a32b..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.bodyhandler; - -import org.asynchttpclient.Request; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpRequestPacket; - -import java.io.IOException; - -public abstract class BodyHandler { - - public static int MAX_CHUNK_SIZE = 8192; - - public abstract boolean handlesBodyType(final Request request); - - public abstract boolean doHandle(final FilterChainContext ctx, - final Request request, final HttpRequestPacket requestPacket) - throws IOException; - - /** - * Tries to predict request content-length based on the {@link Request}. - * Not all the BodyHandlers can predict the content-length in advance. - * - * @param request - * @return the content-length, or -1 if the content-length can't be - * predicted - */ - protected long getContentLength(final Request request) { - return request.getContentLength(); - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandlerFactory.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandlerFactory.java deleted file mode 100644 index 8b5cf4ff63..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandlerFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.bodyhandler; - -import org.asynchttpclient.Request; -import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; - -public final class BodyHandlerFactory { - - private final BodyHandler[] handlers; - - public BodyHandlerFactory(GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider) { - handlers = new BodyHandler[] {// - new StringBodyHandler(grizzlyAsyncHttpProvider),// - new ByteArrayBodyHandler(grizzlyAsyncHttpProvider),// - new ParamsBodyHandler(grizzlyAsyncHttpProvider),// - new StreamDataBodyHandler(),// - new PartsBodyHandler(),// - new FileBodyHandler(grizzlyAsyncHttpProvider),// - new BodyGeneratorBodyHandler() // - }; - } - - public BodyHandler getBodyHandler(final Request request) { - for (int i = 0, len = handlers.length; i < len; i++) { - final BodyHandler h = handlers[i]; - if (h.handlesBodyType(request)) { - return h; - } - } - - return null; - } - -} // END BodyHandlerFactory diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ByteArrayBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ByteArrayBodyHandler.java deleted file mode 100644 index 2a16c06b60..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ByteArrayBodyHandler.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.bodyhandler; - -import org.asynchttpclient.Request; -import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.http.HttpRequestPacket; -import org.glassfish.grizzly.memory.Buffers; -import org.glassfish.grizzly.memory.MemoryManager; - -import java.io.IOException; - -public final class ByteArrayBodyHandler extends BodyHandler { - - private final boolean compressionEnabled; - - public ByteArrayBodyHandler( - final GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider) { - compressionEnabled = grizzlyAsyncHttpProvider.getClientConfig().isCompressionEnabled(); - } - - // -------------------------------------------- Methods from BodyHandler - - public boolean handlesBodyType(final Request request) { - return (request.getByteData() != null); - } - - @SuppressWarnings({ "unchecked" }) - public boolean doHandle(final FilterChainContext ctx, final Request request, - final HttpRequestPacket requestPacket) throws IOException { - - final byte[] data = request.getByteData(); - final MemoryManager mm = ctx.getMemoryManager(); - final Buffer gBuffer = Buffers.wrap(mm, data); - if (requestPacket.getContentLength() == -1) { - if (!compressionEnabled) { - requestPacket.setContentLengthLong(data.length); - } - } - final HttpContent content = requestPacket.httpContentBuilder().content(gBuffer).build(); - content.setLast(true); - ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null)); - return true; - } - - @Override - protected long getContentLength(final Request request) { - if (request.getContentLength() >= 0) { - return request.getContentLength(); - } - - return compressionEnabled ? -1 : request.getByteData().length; - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ExpectHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ExpectHandler.java deleted file mode 100644 index f322aa80dc..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ExpectHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.bodyhandler; - -import org.asynchttpclient.Request; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpRequestPacket; - -import java.io.IOException; - -public final class ExpectHandler extends BodyHandler { - - private final BodyHandler delegate; - private Request request; - private HttpRequestPacket requestPacket; - - // -------------------------------------------------------- Constructors - - public ExpectHandler(final BodyHandler delegate) { - this.delegate = delegate; - } - - // -------------------------------------------- Methods from BodyHandler - - public boolean handlesBodyType(Request request) { - return delegate.handlesBodyType(request); - } - - @SuppressWarnings({ "unchecked" }) - public boolean doHandle(FilterChainContext ctx, Request request, HttpRequestPacket requestPacket) throws IOException { - this.request = request; - this.requestPacket = requestPacket; - - // Set content-length if possible - final long contentLength = delegate.getContentLength(request); - if (contentLength != -1) { - requestPacket.setContentLengthLong(contentLength); - } - - ctx.write(requestPacket, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null)); - return true; - } - - public void finish(final FilterChainContext ctx) throws IOException { - delegate.doHandle(ctx, request, requestPacket); - } - -} // END ContinueHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/FileBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/FileBodyHandler.java deleted file mode 100644 index 70bed73210..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/FileBodyHandler.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.bodyhandler; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.Request; -import org.asynchttpclient.listener.TransferCompletionHandler; -import org.asynchttpclient.providers.grizzly.HttpTxContext; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.EmptyCompletionHandler; -import org.glassfish.grizzly.FileTransfer; -import org.glassfish.grizzly.WriteResult; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.http.HttpRequestPacket; -import org.glassfish.grizzly.memory.Buffers; -import org.glassfish.grizzly.memory.MemoryManager; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; -import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; - -public final class FileBodyHandler extends BodyHandler { - - private static final boolean SEND_FILE_SUPPORT; - static { - SEND_FILE_SUPPORT = configSendFileSupport(); - } - - private final boolean compressionEnabled; - - public FileBodyHandler( - final GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider) { - compressionEnabled = grizzlyAsyncHttpProvider.getClientConfig().isCompressionEnabled(); - } - - // ------------------------------------------------ Methods from BodyHandler - - public boolean handlesBodyType(final Request request) { - return request.getFile() != null; - } - - @SuppressWarnings({ "unchecked" }) - public boolean doHandle(final FilterChainContext ctx, final Request request, final HttpRequestPacket requestPacket) throws IOException { - - final File f = request.getFile(); - requestPacket.setContentLengthLong(f.length()); - final HttpTxContext context = HttpTxContext.get(ctx); - if (compressionEnabled || !SEND_FILE_SUPPORT || requestPacket.isSecure()) { - final FileInputStream fis = new FileInputStream(request.getFile()); - final MemoryManager mm = ctx.getMemoryManager(); - AtomicInteger written = new AtomicInteger(); - boolean last = false; - try { - for (byte[] buf = new byte[MAX_CHUNK_SIZE]; !last;) { - Buffer b = null; - int read; - if ((read = fis.read(buf)) < 0) { - last = true; - b = Buffers.EMPTY_BUFFER; - } - if (b != Buffers.EMPTY_BUFFER) { - written.addAndGet(read); - b = Buffers.wrap(mm, buf, 0, read); - } - - final HttpContent content = requestPacket.httpContentBuilder().content(b).last(last).build(); - ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null)); - } - } finally { - try { - fis.close(); - } catch (IOException ignored) { - } - } - } else { - // write the headers - ctx.write(requestPacket, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null)); - ctx.write(new FileTransfer(f), new EmptyCompletionHandler() { - - @Override - public void updated(WriteResult result) { - notifyHandlerIfNeeded(context, requestPacket, result); - } - - @Override - public void completed(WriteResult result) { - notifyHandlerIfNeeded(context, requestPacket, result); - } - }); - } - - return true; - } - - @Override - protected long getContentLength(final Request request) { - if (request.getContentLength() >= 0) { - return request.getContentLength(); - } - - return compressionEnabled ? -1 : request.getFile().length(); - } - - // --------------------------------------------------------- Private Methods - - private static void notifyHandlerIfNeeded(final HttpTxContext context, final HttpRequestPacket requestPacket, - final WriteResult writeResult) { - final AsyncHandler handler = context.getHandler(); - if (handler != null) { - if (handler instanceof TransferCompletionHandler) { - // WriteResult keeps a track of the total amount written, - // so we need to calculate the delta ourselves. - final long resultTotal = writeResult.getWrittenSize(); - final long written = (resultTotal - context.getTotalBodyWritten().get()); - final long total = context.getTotalBodyWritten().addAndGet(written); - ((TransferCompletionHandler) handler).onContentWriteProgress(written, total, requestPacket.getContentLength()); - } - } - } - - private static boolean configSendFileSupport() { - return !((System.getProperty("os.name").equalsIgnoreCase("linux") && !linuxSendFileSupported()) || System.getProperty("os.name") - .equalsIgnoreCase("HP-UX")); - } - - private static boolean linuxSendFileSupported() { - final String version = System.getProperty("java.version"); - if (version.startsWith("1.6")) { - int idx = version.indexOf('_'); - if (idx == -1) { - return false; - } - final int patchRev = Integer.parseInt(version.substring(idx + 1)); - return (patchRev >= 18); - } else { - return version.startsWith("1.7") || version.startsWith("1.8"); - } - } - -} // END FileBodyHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/NoBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/NoBodyHandler.java deleted file mode 100644 index c3865952bf..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/NoBodyHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.bodyhandler; - -import org.asynchttpclient.Request; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.http.HttpRequestPacket; -import org.glassfish.grizzly.memory.Buffers; - -import java.io.IOException; - -public final class NoBodyHandler extends BodyHandler { - - // -------------------------------------------- Methods from BodyHandler - - public boolean handlesBodyType(final Request request) { - return false; - } - - @SuppressWarnings({ "unchecked" }) - public boolean doHandle(final FilterChainContext ctx, final Request request, final HttpRequestPacket requestPacket) throws IOException { - - final HttpContent content = requestPacket.httpContentBuilder().content(Buffers.EMPTY_BUFFER).build(); - content.setLast(true); - ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null)); - return true; - } - -} // END NoBodyHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java deleted file mode 100644 index 47941e1933..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.bodyhandler; - -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.Param; -import org.asynchttpclient.Request; -import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.http.HttpRequestPacket; -import org.glassfish.grizzly.memory.Buffers; -import org.glassfish.grizzly.memory.MemoryManager; -import org.glassfish.grizzly.utils.Charsets; - -import java.io.IOException; -import java.net.URLEncoder; -import java.util.List; - -public final class ParamsBodyHandler extends BodyHandler { - - private final boolean compressionEnabled; - - public ParamsBodyHandler(GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider) { - compressionEnabled = grizzlyAsyncHttpProvider.getClientConfig().isCompressionEnabled(); - } - - // -------------------------------------------- Methods from BodyHandler - - public boolean handlesBodyType(final Request request) { - final List params = request.getFormParams(); - return isNonEmpty(params); - } - - @SuppressWarnings({ "unchecked" }) - public boolean doHandle(final FilterChainContext ctx, final Request request, final HttpRequestPacket requestPacket) throws IOException { - - if (requestPacket.getContentType() == null) { - requestPacket.setContentType("application/x-www-form-urlencoded"); - } - StringBuilder sb = null; - String charset = request.getBodyEncoding(); - if (charset == null) { - charset = Charsets.ASCII_CHARSET.name(); - } - final List params = request.getFormParams(); - if (!params.isEmpty()) { - if (sb == null) { - sb = new StringBuilder(128); - } - for (Param param : params) { - sb.append(URLEncoder.encode(param.getName(), charset)).append('=').append(URLEncoder.encode(param.getValue(), charset)); - sb.append('&'); - } - sb.setLength(sb.length() - 1); - } - if (sb != null) { - final byte[] data = sb.toString().getBytes(charset); - final MemoryManager mm = ctx.getMemoryManager(); - final Buffer gBuffer = Buffers.wrap(mm, data); - final HttpContent content = requestPacket.httpContentBuilder().content(gBuffer).build(); - if (requestPacket.getContentLength() == -1) { - if (!compressionEnabled) { - requestPacket.setContentLengthLong(data.length); - } - } - content.setLast(true); - ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null)); - } - return true; - } -} // END ParamsBodyHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/PartsBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/PartsBodyHandler.java deleted file mode 100644 index 2c6e506165..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/PartsBodyHandler.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.bodyhandler; - -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.Body; -import org.asynchttpclient.Request; -import org.asynchttpclient.multipart.MultipartBody; -import org.asynchttpclient.multipart.MultipartUtils; -import org.asynchttpclient.multipart.Part; -import org.asynchttpclient.providers.grizzly.FeedableBodyGenerator; -import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpRequestPacket; -import org.glassfish.grizzly.memory.Buffers; -import org.glassfish.grizzly.memory.MemoryManager; - -import java.io.IOException; -import java.util.List; - -public final class PartsBodyHandler extends BodyHandler { - - // -------------------------------------------- Methods from BodyHandler - - public boolean handlesBodyType(final Request request) { - return isNonEmpty(request.getParts()); - } - - public boolean doHandle(final FilterChainContext ctx, final Request request, final HttpRequestPacket requestPacket) throws IOException { - - final List parts = request.getParts(); - final MultipartBody multipartBody = MultipartUtils.newMultipartBody(parts, request.getHeaders()); - requestPacket.setContentLengthLong(multipartBody.getContentLength()); - requestPacket.setContentType(multipartBody.getContentType()); - if (GrizzlyAsyncHttpProvider.LOGGER.isDebugEnabled()) { - GrizzlyAsyncHttpProvider.LOGGER.debug("REQUEST(modified): contentLength={}, contentType={}", - new Object[] { requestPacket.getContentLength(), requestPacket.getContentType() }); - } - - final FeedableBodyGenerator generator = new FeedableBodyGenerator() { - @Override - public Body createBody() throws IOException { - return multipartBody; - } - }; - generator.setFeeder(new FeedableBodyGenerator.BaseFeeder(generator) { - @Override - public void flush() throws IOException { - final Body bodyLocal = feedableBodyGenerator.createBody(); - try { - final MemoryManager mm = ctx.getMemoryManager(); - boolean last = false; - while (!last) { - Buffer buffer = mm.allocate(BodyHandler.MAX_CHUNK_SIZE); - buffer.allowBufferDispose(true); - final long readBytes = bodyLocal.read(buffer.toByteBuffer()); - if (readBytes > 0) { - buffer.position((int) readBytes); - buffer.trim(); - } else { - buffer.dispose(); - if (readBytes < 0) { - last = true; - buffer = Buffers.EMPTY_BUFFER; - } else { - throw new IllegalStateException("MultipartBody unexpectedly returned 0 bytes available"); - } - } - feed(buffer, last); - } - } finally { - if (bodyLocal != null) { - try { - bodyLocal.close(); - } catch (IOException ignore) { - } - } - } - } - }); - generator.initializeAsynchronousTransfer(ctx, requestPacket); - return false; - } -} // END PartsBodyHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StreamDataBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StreamDataBodyHandler.java deleted file mode 100644 index 79eb603242..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StreamDataBodyHandler.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.bodyhandler; - -import static org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider.LOGGER; - -import org.asynchttpclient.Request; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.http.HttpRequestPacket; -import org.glassfish.grizzly.memory.MemoryManager; - -import java.io.IOException; -import java.io.InputStream; - -public final class StreamDataBodyHandler extends BodyHandler { - - // -------------------------------------------- Methods from BodyHandler - - public boolean handlesBodyType(final Request request) { - return (request.getStreamData() != null); - } - - @SuppressWarnings({ "unchecked" }) - public boolean doHandle(final FilterChainContext ctx, final Request request, final HttpRequestPacket requestPacket) throws IOException { - - final MemoryManager mm = ctx.getMemoryManager(); - Buffer buffer = mm.allocate(512); - final byte[] b = new byte[512]; - int read; - final InputStream in = request.getStreamData(); - try { - in.reset(); - } catch (IOException ioe) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(ioe.toString(), ioe); - } - } - if (in.markSupported()) { - in.mark(0); - } - - while ((read = in.read(b)) != -1) { - if (read > buffer.remaining()) { - buffer = mm.reallocate(buffer, buffer.capacity() + 512); - } - buffer.put(b, 0, read); - } - buffer.trim(); - if (buffer.hasRemaining()) { - final HttpContent content = requestPacket.httpContentBuilder().content(buffer).build(); - buffer.allowBufferDispose(false); - content.setLast(true); - ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null)); - } - return true; - } -} // END StreamDataBodyHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StringBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StringBodyHandler.java deleted file mode 100644 index 7785c1fc87..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StringBodyHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.bodyhandler; - -import org.asynchttpclient.Request; -import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.http.HttpRequestPacket; -import org.glassfish.grizzly.memory.Buffers; -import org.glassfish.grizzly.memory.MemoryManager; -import org.glassfish.grizzly.utils.Charsets; - -import java.io.IOException; - -public final class StringBodyHandler extends BodyHandler { - private final GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider; - - public StringBodyHandler(GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider) { - this.grizzlyAsyncHttpProvider = grizzlyAsyncHttpProvider; - } - - // -------------------------------------------- Methods from BodyHandler - - public boolean handlesBodyType(final Request request) { - return (request.getStringData() != null); - } - - @SuppressWarnings({ "unchecked" }) - public boolean doHandle(final FilterChainContext ctx, final Request request, final HttpRequestPacket requestPacket) throws IOException { - - String charset = request.getBodyEncoding(); - if (charset == null) { - charset = Charsets.ASCII_CHARSET.name(); - } - final byte[] data = request.getStringData().getBytes(charset); - final MemoryManager mm = ctx.getMemoryManager(); - final Buffer gBuffer = Buffers.wrap(mm, data); - if (requestPacket.getContentLength() == -1) { - if (!grizzlyAsyncHttpProvider.getClientConfig().isCompressionEnabled()) { - requestPacket.setContentLengthLong(data.length); - } - } - final HttpContent content = requestPacket.httpContentBuilder().content(gBuffer).build(); - content.setLast(true); - ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null)); - return true; - } -} // END StringBodyHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientEventFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientEventFilter.java deleted file mode 100644 index 687984e6a8..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientEventFilter.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.filters; - -import org.asynchttpclient.providers.grizzly.EventHandler; -import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpClientFilter; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.http.HttpHeader; - -import java.io.IOException; -import org.asynchttpclient.providers.grizzly.filters.events.GracefulCloseEvent; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.filterchain.FilterChainEvent; -import org.glassfish.grizzly.filterchain.NextAction; -import org.glassfish.grizzly.http.HttpResponsePacket; - -/** - * Extension of the {@link HttpClientFilter} that is responsible for handling - * events triggered by the parsing and serialization of HTTP packets. - * - * @since 2.0 - * @author The Grizzly Team - */ -public final class AsyncHttpClientEventFilter extends HttpClientFilter implements GrizzlyAsyncHttpProvider.Cleanup { - - private final EventHandler eventHandler; - - // -------------------------------------------------------- Constructors - - public AsyncHttpClientEventFilter(final EventHandler eventHandler) { - this(eventHandler, DEFAULT_MAX_HTTP_PACKET_HEADER_SIZE); - } - - public AsyncHttpClientEventFilter(final EventHandler eventHandler, final int maxHeaderSize) { - - super(maxHeaderSize); - this.eventHandler = eventHandler; - } - - @Override - public NextAction handleEvent(final FilterChainContext ctx, - final FilterChainEvent event) throws IOException { - if (event.type() == GracefulCloseEvent.class) { - // Connection was closed. - // This event is fired only for responses, which don't have - // associated transfer-encoding or content-length. - // We have to complete such a request-response processing gracefully. - final GracefulCloseEvent closeEvent = (GracefulCloseEvent) event; - final HttpResponsePacket response = closeEvent.getHttpTxContext() - .getResponseStatus().getResponse(); - response.getProcessingState().getHttpContext().attach(ctx); - - onHttpPacketParsed(response, ctx); - - return ctx.getStopAction(); - } - - return ctx.getInvokeAction(); - } - - - @Override - public void exceptionOccurred(FilterChainContext ctx, Throwable error) { - eventHandler.exceptionOccurred(ctx, error); - } - - @Override - protected void onHttpContentParsed(HttpContent content, FilterChainContext ctx) { - eventHandler.onHttpContentParsed(content, ctx); - } - - @Override - protected void onHttpHeadersEncoded(HttpHeader httpHeader, FilterChainContext ctx) { - eventHandler.onHttpHeadersEncoded(httpHeader, ctx); - } - - @Override - protected void onHttpContentEncoded(HttpContent content, FilterChainContext ctx) { - eventHandler.onHttpContentEncoded(content, ctx); - } - - @Override - protected void onInitialLineParsed(HttpHeader httpHeader, FilterChainContext ctx) { - eventHandler.onInitialLineParsed(httpHeader, ctx); - } - - @Override - protected void onHttpHeaderError(HttpHeader httpHeader, FilterChainContext ctx, Throwable t) throws IOException { - eventHandler.onHttpHeaderError(httpHeader, ctx, t); - } - - @Override - protected void onHttpContentError(HttpHeader httpHeader, FilterChainContext ctx, Throwable t) throws IOException { - eventHandler.onHttpContentError(httpHeader, ctx, t); - } - - @Override - protected void onHttpHeadersParsed(HttpHeader httpHeader, FilterChainContext ctx) { - eventHandler.onHttpHeadersParsed(httpHeader, ctx); - } - - @Override - protected boolean onHttpHeaderParsed(final HttpHeader httpHeader, - final Buffer buffer, final FilterChainContext ctx) { - super.onHttpHeaderParsed(httpHeader, buffer, ctx); - return eventHandler.onHttpHeaderParsed(httpHeader, buffer, ctx); - } - - @Override - protected boolean onHttpPacketParsed(HttpHeader httpHeader, FilterChainContext ctx) { - return eventHandler.onHttpPacketParsed(httpHeader, ctx); - } - - @Override - public void cleanup(final FilterChainContext ctx) { - clearResponse(ctx.getConnection()); - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java deleted file mode 100644 index 7d80c95d99..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java +++ /dev/null @@ -1,567 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.filters; - -import static org.asynchttpclient.providers.grizzly.filters.SwitchingSSLFilter.getHandshakeError; -import static org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider.NTLM_ENGINE; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getAuthority; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getNonEmptyPath; -import static org.asynchttpclient.util.AuthenticatorUtils.computeBasicAuthentication; -import static org.asynchttpclient.util.AuthenticatorUtils.computeDigestAuthentication; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.UpgradeHandler; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.listener.TransferCompletionHandler; -import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; -import org.asynchttpclient.providers.grizzly.GrizzlyResponseFuture; -import org.asynchttpclient.providers.grizzly.HttpTxContext; -import org.asynchttpclient.providers.grizzly.RequestInfoHolder; -import org.asynchttpclient.providers.grizzly.Utils; -import org.asynchttpclient.providers.grizzly.bodyhandler.BodyHandler; -import org.asynchttpclient.providers.grizzly.bodyhandler.BodyHandlerFactory; -import org.asynchttpclient.providers.grizzly.bodyhandler.ExpectHandler; -import org.asynchttpclient.providers.grizzly.filters.events.ContinueEvent; -import org.asynchttpclient.providers.grizzly.filters.events.SSLSwitchingEvent; -import org.asynchttpclient.providers.grizzly.filters.events.TunnelRequestEvent; -import org.asynchttpclient.uri.UriComponents; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.Grizzly; -import org.glassfish.grizzly.attributes.Attribute; -import org.glassfish.grizzly.filterchain.BaseFilter; -import org.glassfish.grizzly.filterchain.FilterChain; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.filterchain.FilterChainEvent; -import org.glassfish.grizzly.filterchain.NextAction; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.http.HttpContext; -import org.glassfish.grizzly.http.HttpRequestPacket; -import org.glassfish.grizzly.http.HttpResponsePacket; -import org.glassfish.grizzly.http.Method; -import org.glassfish.grizzly.http.ProcessingState; -import org.glassfish.grizzly.http.Protocol; -import org.glassfish.grizzly.http.util.CookieSerializerUtils; -import org.glassfish.grizzly.http.util.Header; -import org.glassfish.grizzly.http.util.MimeHeaders; -import org.glassfish.grizzly.impl.SafeFutureImpl; -import org.glassfish.grizzly.spdy.SpdySession; -import org.glassfish.grizzly.spdy.SpdyStream; -import org.glassfish.grizzly.ssl.SSLConnectionContext; -import org.glassfish.grizzly.ssl.SSLUtils; -import org.glassfish.grizzly.websockets.Version; -import org.slf4j.Logger; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.locks.Lock; - -/** - * This {@link org.glassfish.grizzly.filterchain.Filter} is typically the last in the {@FilterChain}. Its primary responsibility is converting the async-http-client - * {@link Request} into a Grizzly {@link HttpRequestPacket}. - * - * @since 1.7 - * @author The Grizzly Team - */ -public final class AsyncHttpClientFilter extends BaseFilter { - - private ConcurrentLinkedQueue requestCache = new ConcurrentLinkedQueue(); - private final Logger logger; - - private final AsyncHttpClientConfig config; - private final GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider; - private final BodyHandlerFactory bodyHandlerFactory; - - private static final Attribute PROXY_AUTH_FAILURE = Grizzly.DEFAULT_ATTRIBUTE_BUILDER - .createAttribute(AsyncHttpClientFilter.class.getName() + "-PROXY-AUTH_FAILURE"); - - // -------------------------------------------------------- Constructors - - public AsyncHttpClientFilter(GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider, final AsyncHttpClientConfig config) { - this.grizzlyAsyncHttpProvider = grizzlyAsyncHttpProvider; - this.config = config; - bodyHandlerFactory = new BodyHandlerFactory(grizzlyAsyncHttpProvider); - logger = GrizzlyAsyncHttpProvider.LOGGER; - } - - // --------------------------------------------- Methods from BaseFilter - - @Override - public NextAction handleRead(final FilterChainContext ctx) throws IOException { - final HttpContent httpContent = ctx.getMessage(); - if (httpContent.isLast()) { - // Perform the cleanup logic if it's the last chunk of the payload - final HttpResponsePacket response = (HttpResponsePacket) httpContent.getHttpHeader(); - - recycleRequestResponsePackets(ctx.getConnection(), response); - return ctx.getStopAction(); - } - - return ctx.getInvokeAction(); - } - - @Override - public NextAction handleWrite(final FilterChainContext ctx) throws IOException { - - Object message = ctx.getMessage(); - if (message instanceof RequestInfoHolder) { - ctx.setMessage(null); - if (!sendAsGrizzlyRequest((RequestInfoHolder) message, ctx)) { - return ctx.getSuspendAction(); - } - } else if (message instanceof Buffer) { - return ctx.getInvokeAction(); - } - - return ctx.getStopAction(); - } - - @SuppressWarnings("unchecked") - @Override - public NextAction handleEvent(final FilterChainContext ctx, final FilterChainEvent event) throws IOException { - - final Object type = event.type(); - if (type == ContinueEvent.class) { - final ContinueEvent continueEvent = (ContinueEvent) event; - ((ExpectHandler) continueEvent.getContext().getBodyHandler()).finish(ctx); - } else if (type == TunnelRequestEvent.class) { - // Disable SSL for the time being... - ctx.notifyDownstream(new SSLSwitchingEvent(false, ctx.getConnection())); - ctx.suspend(); - TunnelRequestEvent tunnelRequestEvent = (TunnelRequestEvent) event; - final ProxyServer proxyServer = tunnelRequestEvent.getProxyServer(); - final UriComponents requestUri = tunnelRequestEvent.getUri(); - - RequestBuilder builder = new RequestBuilder(); - builder.setMethod(Method.CONNECT.getMethodString()); - builder.setUrl("http://" + getAuthority(requestUri)); - Request request = builder.build(); - - AsyncHandler handler = new AsyncCompletionHandler() { - @Override - public Object onCompleted(Response response) throws Exception { - if (response.getStatusCode() != 200) { - PROXY_AUTH_FAILURE.set(ctx.getConnection(), Boolean.TRUE); - } - ctx.notifyDownstream(new SSLSwitchingEvent(true, ctx.getConnection())); - ctx.notifyDownstream(event); - return response; - } - }; - final GrizzlyResponseFuture future = new GrizzlyResponseFuture(grizzlyAsyncHttpProvider, request, handler, proxyServer); - future.setDelegate(SafeFutureImpl.create()); - - grizzlyAsyncHttpProvider.execute(ctx.getConnection(), request, handler, future, HttpTxContext.get(ctx)); - return ctx.getSuspendAction(); - } - - return ctx.getStopAction(); - } - - // ----------------------------------------------------- Private Methods - - private static void recycleRequestResponsePackets(final Connection c, final HttpResponsePacket response) { - if (!Utils.isSpdyConnection(c)) { - HttpRequestPacket request = response.getRequest(); - request.setExpectContent(false); - response.recycle(); - request.recycle(); - } - } - - private boolean sendAsGrizzlyRequest( - final RequestInfoHolder requestInfoHolder, - final FilterChainContext ctx) throws IOException { - - HttpTxContext httpTxContext = requestInfoHolder.getHttpTxContext(); - if (httpTxContext == null) { - httpTxContext = HttpTxContext.create(requestInfoHolder); - } - - if (checkProxyAuthFailure(ctx, httpTxContext)) { - return true; - } - - final Request request = httpTxContext.getRequest(); - final UriComponents uri = request.getURI(); - boolean secure = Utils.isSecure(uri); - boolean isWebSocket = isWSRequest(httpTxContext.getRequestUri()); - - // If the request is secure, check to see if an error occurred during - // the handshake. We have to do this here, as the error would occur - // out of the scope of a HttpTxContext so there would be - // no good way to communicate the problem to the caller. - if (secure && checkHandshakeError(ctx, httpTxContext)) { - return true; - } - - - if (isUpgradeRequest(httpTxContext.getHandler()) && isWebSocket) { - httpTxContext.setWSRequest(true); - convertToUpgradeRequest(httpTxContext); - } - - HttpRequestPacket requestPacket = requestCache.poll(); - if (requestPacket == null) { - requestPacket = new HttpRequestPacketImpl(); - } - - final Method method = Method.valueOf(request.getMethod()); - - requestPacket.setMethod(method); - requestPacket.setProtocol(Protocol.HTTP_1_1); - - // Special handling for CONNECT. - if (method == Method.CONNECT) { - final int port = uri.getPort(); - requestPacket.setRequestURI(uri.getHost() + ':' + (port == -1 ? 443 : port)); - } else if ((secure || isWebSocket) && config.isUseRelativeURIsWithConnectProxies()) { - requestPacket.setRequestURI(getNonEmptyPath(uri)); - } else { - requestPacket.setRequestURI(uri.toUrl()); - } - - final BodyHandler bodyHandler = isPayloadAllowed(method) ? - bodyHandlerFactory.getBodyHandler(request) : - null; - - if (bodyHandler != null) { - final long contentLength = request.getContentLength(); - if (contentLength >= 0) { - requestPacket.setContentLengthLong(contentLength); - requestPacket.setChunked(false); - } else { - requestPacket.setChunked(true); - } - } - - if (httpTxContext.isWSRequest()) { - try { - final URI wsURI = httpTxContext.getWsRequestURI().toURI(); - httpTxContext.setProtocolHandler(Version.RFC6455.createHandler(true)); - httpTxContext.setHandshake(httpTxContext.getProtocolHandler().createHandShake(wsURI)); - requestPacket = (HttpRequestPacket) httpTxContext.getHandshake().composeHeaders().getHttpHeader(); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Invalid WS URI: " + httpTxContext.getWsRequestURI()); - } - } - - requestPacket.setSecure(secure); - addQueryString(request, requestPacket); - addHostHeader(request, uri, requestPacket); - addGeneralHeaders(request, requestPacket); - addCookies(request, requestPacket); - addAuthorizationHeader(request, requestPacket); - - initTransferCompletionHandler(request, httpTxContext.getHandler()); - - final HttpRequestPacket requestPacketLocal = requestPacket; - FilterChainContext sendingCtx = ctx; - - if (secure) { - // Check to see if the ProtocolNegotiator has given - // us a different FilterChain to use. If so, we need - // use a different FilterChainContext when invoking sendRequest(). - sendingCtx = checkAndHandleFilterChainUpdate(ctx, sendingCtx); - } - final Connection c = ctx.getConnection(); - final HttpContext httpCtx; - if (!Utils.isSpdyConnection(c)) { - httpCtx = HttpContext.newInstance(c, c, c, requestPacketLocal); - } else { - SpdySession session = SpdySession.get(c); - final Lock lock = session.getNewClientStreamLock(); - try { - lock.lock(); - SpdyStream stream = session.openStream(requestPacketLocal, session.getNextLocalStreamId(), 0, 0, 0, false, - !requestPacketLocal.isExpectContent()); - httpCtx = HttpContext.newInstance(stream, stream, stream, requestPacketLocal); - } finally { - lock.unlock(); - } - } - httpCtx.attach(ctx); - HttpTxContext.set(ctx, httpTxContext); - requestPacketLocal.getProcessingState().setHttpContext(httpCtx); - requestPacketLocal.setConnection(c); - - return sendRequest(sendingCtx, request, requestPacketLocal, - wrapWithExpectHandlerIfNeeded(bodyHandler, requestPacket)); - } - - @SuppressWarnings("unchecked") - public boolean sendRequest(final FilterChainContext ctx, - final Request request, final HttpRequestPacket requestPacket, - final BodyHandler bodyHandler) - throws IOException { - - boolean isWriteComplete = true; - - if (bodyHandler != null) { - final HttpTxContext context = HttpTxContext.get(ctx); - context.setBodyHandler(bodyHandler); - if (logger.isDebugEnabled()) { - logger.debug("REQUEST: {}", requestPacket); - } - isWriteComplete = bodyHandler.doHandle(ctx, request, requestPacket); - } else { - HttpContent content = HttpContent.builder(requestPacket).last(true).build(); - if (logger.isDebugEnabled()) { - logger.debug("REQUEST: {}", requestPacket); - } - ctx.write(content, ctx.getTransportContext().getCompletionHandler()); - } - - return isWriteComplete; - } - - private static FilterChainContext checkAndHandleFilterChainUpdate(final FilterChainContext ctx, final FilterChainContext sendingCtx) { - FilterChainContext ctxLocal = sendingCtx; - SSLConnectionContext sslCtx = SSLUtils.getSslConnectionContext(ctx.getConnection()); - if (sslCtx != null) { - FilterChain fc = sslCtx.getNewConnectionFilterChain(); - - if (fc != null) { - // Create a new FilterChain context using the new - // FilterChain. - // TODO: We need to mark this connection somehow - // as being only suitable for this type of - // request. - ctxLocal = obtainProtocolChainContext(ctx, fc); - } - } - return ctxLocal; - } - - /** - * check if we need to wrap the BodyHandler with ExpectHandler - */ - private static BodyHandler wrapWithExpectHandlerIfNeeded( - final BodyHandler bodyHandler, - final HttpRequestPacket requestPacket) { - - if (bodyHandler == null) { - return null; - } - - // check if we need to wrap the BodyHandler with ExpectHandler - final MimeHeaders headers = requestPacket.getHeaders(); - final int expectHeaderIdx = headers.indexOf(Header.Expect, 0); - - return expectHeaderIdx != -1 - && headers.getValue(expectHeaderIdx).equalsIgnoreCase("100-Continue") - ? new ExpectHandler(bodyHandler) - : bodyHandler; - } - - private static boolean isPayloadAllowed(final Method method) { - return method.getPayloadExpectation() != Method.PayloadExpectation.NOT_ALLOWED; - } - - private static void initTransferCompletionHandler(final Request request, final AsyncHandler h) throws IOException { - if (h instanceof TransferCompletionHandler) { - final FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(request.getHeaders()); - TransferCompletionHandler.class.cast(h).headers(map); - } - } - - private static boolean checkHandshakeError(final FilterChainContext ctx, final HttpTxContext httpCtx) { - Throwable t = getHandshakeError(ctx.getConnection()); - if (t != null) { - httpCtx.abort(t); - return true; - } - return false; - } - - private static boolean checkProxyAuthFailure(final FilterChainContext ctx, final HttpTxContext httpCtx) { - final Boolean failed = PROXY_AUTH_FAILURE.get(ctx.getConnection()); - if (failed != null && failed) { - httpCtx.abort(new IllegalStateException("Unable to authenticate with proxy")); - return true; - } - return false; - } - - private static FilterChainContext obtainProtocolChainContext(final FilterChainContext ctx, final FilterChain completeProtocolFilterChain) { - - final FilterChainContext newFilterChainContext = completeProtocolFilterChain.obtainFilterChainContext(ctx.getConnection(), - ctx.getStartIdx() + 1, completeProtocolFilterChain.size(), ctx.getFilterIdx() + 1); - - newFilterChainContext.setAddressHolder(ctx.getAddressHolder()); - newFilterChainContext.setMessage(ctx.getMessage()); - newFilterChainContext.getInternalContext().setIoEvent(ctx.getInternalContext().getIoEvent()); - ctx.getConnection().setProcessor(completeProtocolFilterChain); - return newFilterChainContext; - } - - private static void addHostHeader(final Request request, - final UriComponents uri, final HttpRequestPacket requestPacket) { - if (!request.getHeaders().containsKey(Header.Host.toString())) { - String host = request.getVirtualHost(); - if (host != null) { - requestPacket.addHeader(Header.Host, host); - } else { - if (uri.getPort() == -1) { - requestPacket.addHeader(Header.Host, uri.getHost()); - } else { - requestPacket.addHeader(Header.Host, uri.getHost() + ':' + uri.getPort()); - } - } - } - } - - private void addAuthorizationHeader(final Request request, final HttpRequestPacket requestPacket) { - Realm realm = request.getRealm(); - if (realm == null) { - realm = config.getRealm(); - } - if (realm != null && realm.getUsePreemptiveAuth()) { - final String authHeaderValue = generateAuthHeader(realm); - if (authHeaderValue != null) { - requestPacket.addHeader(Header.Authorization, authHeaderValue); - } - } - } - - private String generateAuthHeader(final Realm realm) { - try { - switch (realm.getAuthScheme()) { - case BASIC: - return computeBasicAuthentication(realm); - case DIGEST: - return computeDigestAuthentication(realm); - case NTLM: - return NTLM_ENGINE.generateType1Msg("NTLM " + realm.getNtlmDomain(), realm.getNtlmHost()); - default: - return null; - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static boolean isUpgradeRequest(final AsyncHandler handler) { - return (handler instanceof UpgradeHandler); - } - - private static boolean isWSRequest(final UriComponents requestUri) { - return requestUri.getScheme().startsWith("ws"); - } - - private static void convertToUpgradeRequest(final HttpTxContext ctx) { - - final UriComponents requestUri = ctx.getRequestUri(); - - ctx.setWsRequestURI(requestUri); - ctx.setRequestUri(requestUri.withNewScheme( - "ws".equals(requestUri.getScheme()) - ? "http" - : "https")); - } - - private void addGeneralHeaders(final Request request, final HttpRequestPacket requestPacket) { - - if (isNonEmpty(request.getHeaders())) { - final FluentCaseInsensitiveStringsMap map = request.getHeaders(); - for (final Map.Entry> entry : map.entrySet()) { - final String headerName = entry.getKey(); - final List headerValues = entry.getValue(); - if (isNonEmpty(headerValues)) { - for (int i = 0, len = headerValues.size(); i < len; i++) { - requestPacket.addHeader(headerName, headerValues.get(i)); - } - } - } - } - - final MimeHeaders headers = requestPacket.getHeaders(); - if (!headers.contains(Header.Connection)) { - // final boolean canCache = context.provider.clientConfig.getAllowPoolingConnection(); - requestPacket.addHeader(Header.Connection, /* (canCache ? */"keep-alive" /* : "close") */); - } - - if (!headers.contains(Header.Accept)) { - requestPacket.addHeader(Header.Accept, "*/*"); - } - - if (!headers.contains(Header.UserAgent)) { - requestPacket.addHeader(Header.UserAgent, config.getUserAgent()); - } - } - - private void addCookies(final Request request, final HttpRequestPacket requestPacket) { - - final Collection cookies = request.getCookies(); - if (isNonEmpty(cookies)) { - StringBuilder sb = new StringBuilder(128); - org.glassfish.grizzly.http.Cookie[] gCookies = new org.glassfish.grizzly.http.Cookie[cookies.size()]; - convertCookies(cookies, gCookies); - CookieSerializerUtils.serializeClientCookies(sb, false, true, gCookies); - requestPacket.addHeader(Header.Cookie, sb.toString()); - } - } - - private static void convertCookies(final Collection cookies, - final org.glassfish.grizzly.http.Cookie[] gCookies) { - int idx = 0; - if (!cookies.isEmpty()) { - for (final Cookie cookie : cookies) { - gCookies[idx++] = new org.glassfish.grizzly.http.Cookie( - cookie.getName(), cookie.getValue()); - } - } - } - - private static void addQueryString(final Request request, final HttpRequestPacket requestPacket) { - - String query = request.getURI().getQuery(); - if (isNonEmpty(query)) { - requestPacket.setQueryString(query); - } - } - - class HttpRequestPacketImpl extends HttpRequestPacket { - - private ProcessingState processingState = new ProcessingState(); - - // -------------------------------------- Methods from HttpRequestPacketImpl - - @Override - public ProcessingState getProcessingState() { - return processingState; - } - - @Override - public void recycle() { - super.recycle(); - processingState.recycle(); - requestCache.add(this); - } - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncSpdyClientEventFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncSpdyClientEventFilter.java deleted file mode 100644 index 758c6e7783..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncSpdyClientEventFilter.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.filters; - -import org.asynchttpclient.providers.grizzly.EventHandler; -import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.http.HttpHeader; -import org.glassfish.grizzly.spdy.SpdyHandlerFilter; -import org.glassfish.grizzly.spdy.SpdyMode; - -import java.io.IOException; -import java.util.concurrent.ExecutorService; - -/** - * Extension of the {@link SpdyHandlerFilter} that is responsible for handling - * events triggered by the parsing and serialization of HTTP packets. - * - * @since 2.0 - * @author The Grizzly Team - */ -public final class AsyncSpdyClientEventFilter extends SpdyHandlerFilter implements GrizzlyAsyncHttpProvider.Cleanup { - - private final EventHandler eventHandler; - - // -------------------------------------------------------- Constructors - - public AsyncSpdyClientEventFilter(final EventHandler eventHandler, SpdyMode mode, ExecutorService threadPool) { - super(mode, threadPool); - this.eventHandler = eventHandler; - } - - @Override - public void exceptionOccurred(FilterChainContext ctx, Throwable error) { - eventHandler.exceptionOccurred(ctx, error); - } - - @Override - protected void onHttpContentParsed(HttpContent content, FilterChainContext ctx) { - eventHandler.onHttpContentParsed(content, ctx); - } - - @Override - protected void onHttpHeadersEncoded(HttpHeader httpHeader, FilterChainContext ctx) { - eventHandler.onHttpHeadersEncoded(httpHeader, ctx); - } - - @Override - protected void onHttpContentEncoded(HttpContent content, FilterChainContext ctx) { - eventHandler.onHttpContentEncoded(content, ctx); - } - - @Override - protected void onInitialLineParsed(HttpHeader httpHeader, FilterChainContext ctx) { - eventHandler.onInitialLineParsed(httpHeader, ctx); - } - - @Override - protected void onHttpHeaderError(HttpHeader httpHeader, FilterChainContext ctx, Throwable t) throws IOException { - eventHandler.onHttpHeaderError(httpHeader, ctx, t); - } - - @Override - protected void onHttpContentError(HttpHeader httpHeader, FilterChainContext ctx, Throwable t) throws IOException { - eventHandler.onHttpContentError(httpHeader, ctx, t); - } - - - @Override - protected void onHttpHeadersParsed(HttpHeader httpHeader, FilterChainContext ctx) { - eventHandler.onHttpHeadersParsed(httpHeader, ctx); - } - - @Override - protected boolean onHttpPacketParsed(HttpHeader httpHeader, FilterChainContext ctx) { - return eventHandler.onHttpPacketParsed(httpHeader, ctx); - } - - @Override - public void cleanup(FilterChainContext ctx) { - - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/ClientEncodingFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/ClientEncodingFilter.java deleted file mode 100644 index b3d7fa6439..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/ClientEncodingFilter.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2013 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.providers.grizzly.filters; - -import org.glassfish.grizzly.http.EncodingFilter; -import org.glassfish.grizzly.http.HttpHeader; -import org.glassfish.grizzly.http.HttpResponsePacket; -import org.glassfish.grizzly.http.util.DataChunk; -import org.glassfish.grizzly.http.util.Header; - -/** - * {@link EncodingFilter} to enable gzip encoding. - * - * @since 1.7 - * @author The Grizzly Team - */ -public final class ClientEncodingFilter implements EncodingFilter { - - // --------------------------------------------- Methods from EncodingFilter - - public boolean applyEncoding(HttpHeader httpPacket) { - httpPacket.addHeader(Header.AcceptEncoding, "gzip"); - return false; - } - - public boolean applyDecoding(HttpHeader httpPacket) { - - final HttpResponsePacket httpResponse = (HttpResponsePacket) httpPacket; - final DataChunk bc = httpResponse.getHeaders().getValue(Header.ContentEncoding); - return bc != null && bc.indexOf("gzip", 0) != -1; - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/ProxyFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/ProxyFilter.java deleted file mode 100644 index 4b30c7c6cf..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/ProxyFilter.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.filters; - -import static org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider.NTLM_ENGINE; -import static org.asynchttpclient.util.AuthenticatorUtils.computeBasicAuthentication; -import static org.asynchttpclient.util.AuthenticatorUtils.computeDigestAuthentication; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.providers.grizzly.HttpTxContext; -import org.glassfish.grizzly.filterchain.BaseFilter; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.filterchain.NextAction; -import org.glassfish.grizzly.http.HttpRequestPacket; -import org.glassfish.grizzly.http.util.Header; - -import java.io.IOException; -import org.glassfish.grizzly.http.HttpPacket; - -/** - * This Filter will be placed in the FilterChain when a request is being - * proxied. It's main responsibility is to adjust the incoming request - * as appropriate for a proxy to properly handle it. - * - * @since 2.0.0 - * @author The Grizzly Team - */ -public final class ProxyFilter extends BaseFilter { - - private final ProxyServer proxyServer; - private final AsyncHttpClientConfig config; - private final Boolean secure; - - // ------------------------------------------------------------ Constructors - - public ProxyFilter(final ProxyServer proxyServer, final AsyncHttpClientConfig config, boolean secure) { - this.proxyServer = proxyServer; - this.config = config; - this.secure = secure; - } - - // ----------------------------------------------------- Methods from Filter - - @Override - public NextAction handleWrite(FilterChainContext ctx) throws IOException { - final Object msg = ctx.getMessage(); - if (HttpPacket.isHttp(msg)) { - HttpPacket httpPacket = (HttpPacket) msg; - final HttpRequestPacket request = (HttpRequestPacket) httpPacket.getHttpHeader(); - if (!request.isCommitted()) { - HttpTxContext context = HttpTxContext.get(ctx); - assert (context != null); - Request req = context.getRequest(); - if (!secure) { - request.setRequestURI(req.getURI().toUrl()); - } - addProxyHeaders(getRealm(req), request); - } - } - - return ctx.getInvokeAction(); - } - - // --------------------------------------------------------- Private Methods - - private void addProxyHeaders(final Realm realm, final HttpRequestPacket request) { - if (realm != null && realm.getUsePreemptiveAuth()) { - final String authHeaderValue = generateAuthHeader(realm); - if (authHeaderValue != null) { - request.setHeader(Header.ProxyAuthorization, authHeaderValue); - } - } - } - - private Realm getRealm(final Request request) { - Realm realm = request.getRealm(); - if (realm == null) { - realm = config.getRealm(); - } - return realm; - } - - private String generateAuthHeader(final Realm realm) { - try { - switch (realm.getAuthScheme()) { - case BASIC: - return computeBasicAuthentication(realm); - case DIGEST: - return computeDigestAuthentication(realm); - case NTLM: - return NTLM_ENGINE.generateType1Msg("NTLM " + realm.getNtlmDomain(), realm.getNtlmHost()); - default: - return null; - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/SwitchingSSLFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/SwitchingSSLFilter.java deleted file mode 100644 index b0449e82d2..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/SwitchingSSLFilter.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.filters; - -import java.io.IOException; -import java.net.ConnectException; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLSession; -import org.asynchttpclient.providers.grizzly.filters.events.SSLSwitchingEvent; -import org.asynchttpclient.util.Base64; -import org.glassfish.grizzly.CompletionHandler; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.EmptyCompletionHandler; -import org.glassfish.grizzly.Grizzly; -import org.glassfish.grizzly.IOEvent; -import org.glassfish.grizzly.attributes.Attribute; -import org.glassfish.grizzly.filterchain.FilterChain; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.filterchain.FilterChainEvent; -import org.glassfish.grizzly.filterchain.NextAction; -import org.glassfish.grizzly.ssl.SSLEngineConfigurator; -import org.glassfish.grizzly.ssl.SSLFilter; -import org.glassfish.grizzly.ssl.SSLUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * SSL Filter that may be present within the FilterChain and may be - * enabled/disabled by sending the appropriate {@link SSLSwitchingEvent}. - * - * @since 2.0 - * @author The Grizzly Team - */ -public final class SwitchingSSLFilter extends SSLFilter { - - private static final Attribute CONNECTION_IS_SECURE = Grizzly.DEFAULT_ATTRIBUTE_BUILDER - .createAttribute(SwitchingSSLFilter.class.getName()); - private static final Attribute HANDSHAKE_ERROR = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(SwitchingSSLFilter.class - .getName() + "-HANDSHAKE-ERROR"); - - private final static Logger LOGGER = LoggerFactory.getLogger(SwitchingSSLFilter.class); - - // ------------------------------------------------------------ Constructors - - public SwitchingSSLFilter(final SSLEngineConfigurator clientConfig) { - super(null, clientConfig); - } - - // -------------------------------------------------- Methods from SSLFilter - - @Override - protected void notifyHandshakeFailed(Connection connection, Throwable t) { - setError(connection, t); - } - - @Override - public NextAction handleConnect(final FilterChainContext ctx) throws IOException { - // Suspend further handleConnect processing. We do this to ensure that - // the SSL handshake has been completed before returning the connection - // for use in processing user requests. Additionally, this allows us - // to determine if a connection is SPDY or HTTP as early as possible. - ctx.suspend(); - final Connection c = ctx.getConnection(); - handshake(ctx.getConnection(), new EmptyCompletionHandler() { - @Override - public void completed(SSLEngine result) { - // Handshake was successful. Resume the handleConnect - // processing. We pass in Invoke Action so the filter - // chain will call handleConnect on the next filter. - ctx.resume(ctx.getInvokeAction()); - } - - @Override - public void cancelled() { - // Handshake was cancelled. Stop the handleConnect - // processing. The exception will be checked and - // passed to the user later. - setError(c, new SSLHandshakeException("Handshake canceled.")); - ctx.resume(ctx.getStopAction()); - } - - @Override - public void failed(Throwable throwable) { - // Handshake failed. Stop the handleConnect - // processing. The exception will be checked and - // passed to the user later. - setError(c, throwable); - ctx.resume(ctx.getStopAction()); - } - }); - - // This typically isn't advised, however, we need to be able to - // read the response from the proxy and OP_READ isn't typically - // enabled on the connection until all of the handleConnect() - // processing is complete. - enableRead(c); - - // Tell the FilterChain that we're suspending further handleConnect - // processing. - return ctx.getSuspendAction(); - } - - @Override - public NextAction handleEvent(final FilterChainContext ctx, final FilterChainEvent event) throws IOException { - - if (event.type() == SSLSwitchingEvent.class) { - final SSLSwitchingEvent se = (SSLSwitchingEvent) event; - setSecureStatus(se.getConnection(), se.isSecure()); - return ctx.getStopAction(); - } - return ctx.getInvokeAction(); - } - - @Override - public NextAction handleRead(final FilterChainContext ctx) throws IOException { - - if (isSecure(ctx.getConnection())) { - return super.handleRead(ctx); - } - return ctx.getInvokeAction(); - } - - @Override - public NextAction handleWrite(final FilterChainContext ctx) throws IOException { - - if (isSecure(ctx.getConnection())) { - return super.handleWrite(ctx); - } - return ctx.getInvokeAction(); - } - - @Override - public void onFilterChainChanged(final FilterChain filterChain) { - // no-op - } - - public static Throwable getHandshakeError(final Connection c) { - return HANDSHAKE_ERROR.remove(c); - } - - // --------------------------------------------------------- Private Methods - - private static boolean isSecure(final Connection c) { - Boolean secStatus = CONNECTION_IS_SECURE.get(c); - return (secStatus == null ? true : secStatus); - } - - private static void setSecureStatus(final Connection c, final boolean secure) { - CONNECTION_IS_SECURE.set(c, secure); - } - - private static void setError(final Connection c, Throwable t) { - HANDSHAKE_ERROR.set(c, t); - } - - private static void enableRead(final Connection c) throws IOException { - c.enableIOEvent(IOEvent.READ); - } - - // ================= HostnameVerifier section ======================== - - public static CompletionHandler wrapWithHostnameVerifierHandler( - final CompletionHandler delegateCompletionHandler, - final HostnameVerifier verifier, final String host) { - - return new CompletionHandler() { - - public void cancelled() { - if (delegateCompletionHandler != null) { - delegateCompletionHandler.cancelled(); - } - } - - public void failed(final Throwable throwable) { - if (delegateCompletionHandler != null) { - delegateCompletionHandler.failed(throwable); - } - } - - public void completed(final Connection connection) { - if (getHandshakeError(connection) == null) { - final SSLSession session = SSLUtils.getSSLEngine(connection).getSession(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("SSL Handshake onComplete: session = {}, id = {}, isValid = {}, host = {}", - session.toString(), Base64.encode(session.getId()), session.isValid(), host); - } - - if (!verifier.verify(host, session)) { - connection.terminateSilently(); - - if (delegateCompletionHandler != null) { - IOException e = new ConnectException("Host name verification failed for host " + host); - delegateCompletionHandler.failed(e); - } - - return; - } - } - - if (delegateCompletionHandler != null) { - delegateCompletionHandler.completed(connection); - } - } - - public void updated(final Connection connection) { - if (delegateCompletionHandler != null) { - delegateCompletionHandler.updated(connection); - } - } - }; - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/TunnelFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/TunnelFilter.java deleted file mode 100644 index aae301883f..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/TunnelFilter.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.filters; - -import java.io.IOException; - -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.providers.grizzly.Utils; -import org.asynchttpclient.providers.grizzly.filters.events.TunnelRequestEvent; -import org.asynchttpclient.uri.UriComponents; -import org.glassfish.grizzly.IOEvent; -import org.glassfish.grizzly.filterchain.BaseFilter; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.filterchain.FilterChainEvent; -import org.glassfish.grizzly.filterchain.NextAction; - -/** - * This Filter is responsible for HTTP CONNECT - * tunnelling when a connection should be secure and required to - * go through a proxy. - * - * @since 2.0 - * @author The Grizzly Team - */ -public final class TunnelFilter extends BaseFilter { - - private final ProxyServer proxyServer; - private final UriComponents uri; - - // ------------------------------------------------------------ Constructors - - public TunnelFilter(final ProxyServer proxyServer, final UriComponents uri) { - this.proxyServer = proxyServer; - this.uri = uri; - } - - // ----------------------------------------------------- Methods from Filter - - @Override - public NextAction handleConnect(FilterChainContext ctx) throws IOException { - // We suspend the FilterChainContext here to prevent - // notification of other filters of the connection event. - // This allows us to control when the connection is returned - // to the user - we ensure that the tunnel is properly established - // before the user request is sent. - ctx.suspend(); - - // This connection is special and shouldn't be tracked. - Utils.connectionIgnored(ctx.getConnection(), true); - - // This event will be handled by the AsyncHttpClientFilter. - // It will send the CONNECT request and process the response. - // When tunnel is complete, the AsyncHttpClientFilter will - // send this event back to this filter in order to notify - // it that the request processing is complete. - final TunnelRequestEvent tunnelRequestEvent = new TunnelRequestEvent(ctx, proxyServer, uri); - ctx.notifyUpstream(tunnelRequestEvent); - - // This typically isn't advised, however, we need to be able to - // read the response from the proxy and OP_READ isn't typically - // enabled on the connection until all of the handleConnect() - // processing is complete. - ctx.getConnection().enableIOEvent(IOEvent.READ); - - // Tell the FilterChain that we're suspending further handleConnect - // processing. - return ctx.getSuspendAction(); - } - - @Override - public NextAction handleEvent(FilterChainContext ctx, FilterChainEvent event) throws IOException { - if (event.type() == TunnelRequestEvent.class) { - TunnelRequestEvent tunnelRequestEvent = (TunnelRequestEvent) event; - - // Clear ignore status. Any further use of the connection will - // be bound by normal AHC connection processing. - Utils.connectionIgnored(ctx.getConnection(), false); - - // Obtain the context that was previously suspended and resume. - // We pass in Invoke Action so the filter chain will call - // handleConnect on the next filter. - FilterChainContext suspendedContext = tunnelRequestEvent.getSuspendedContext(); - suspendedContext.resume(ctx.getInvokeAction()); - - // Stop further event processing. - return ctx.getStopAction(); - } - return ctx.getInvokeAction(); - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/ContinueEvent.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/ContinueEvent.java deleted file mode 100644 index c514d6677d..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/ContinueEvent.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2013 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.providers.grizzly.filters.events; - -import org.asynchttpclient.providers.grizzly.HttpTxContext; -import org.glassfish.grizzly.filterchain.FilterChainEvent; - -/** - * {@link FilterChainEvent} to trigger HTTP 100-Continue processing. - * - * @since 2.0 - * @author The Grizzly Team - */ -public final class ContinueEvent implements FilterChainEvent { - - private final HttpTxContext context; - - // -------------------------------------------------------- Constructors - - public ContinueEvent(final HttpTxContext context) { - this.context = context; - } - - // --------------------------------------- Methods from FilterChainEvent - - @Override - public Object type() { - return ContinueEvent.class; - } - - // ---------------------------------------------------------- Public Methods - - public HttpTxContext getContext() { - return context; - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/GracefulCloseEvent.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/GracefulCloseEvent.java deleted file mode 100644 index 35252b610b..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/GracefulCloseEvent.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2014 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.providers.grizzly.filters.events; - -import org.asynchttpclient.providers.grizzly.HttpTxContext; -import org.glassfish.grizzly.filterchain.FilterChainEvent; - -/** - * {@link FilterChainEvent} to gracefully complete the request-response processing - * when {@link Connection} is getting closed by the remote host. - * - * @since 1.8.7 - * @author The Grizzly Team - */ -public class GracefulCloseEvent implements FilterChainEvent { - private final HttpTxContext httpTxContext; - - public GracefulCloseEvent(HttpTxContext httpTxContext) { - this.httpTxContext = httpTxContext; - } - - public HttpTxContext getHttpTxContext() { - return httpTxContext; - } - - @Override - public Object type() { - return GracefulCloseEvent.class; - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/SSLSwitchingEvent.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/SSLSwitchingEvent.java deleted file mode 100644 index 5fad70d100..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/SSLSwitchingEvent.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2013 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.providers.grizzly.filters.events; - -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.filterchain.FilterChainEvent; - -/** - * {@link FilterChainEvent} to dynamically enable/disable the SSLFilter on - * a per-connection basis. - * - * @since 2.0 - * @author The Grizzly Team - */ -public final class SSLSwitchingEvent implements FilterChainEvent { - - private final boolean secure; - private final Connection connection; - - // ------------------------------------------------------------ Constructors - - public SSLSwitchingEvent(final boolean secure, final Connection c) { - this.secure = secure; - connection = c; - } - - // ------------------------------------------- Methods from FilterChainEvent - - @Override - public Object type() { - return SSLSwitchingEvent.class; - } - - // ---------------------------------------------------------- Public Methods - - public boolean isSecure() { - return secure; - } - - public Connection getConnection() { - return connection; - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/TunnelRequestEvent.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/TunnelRequestEvent.java deleted file mode 100644 index 0e403dd014..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/TunnelRequestEvent.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2013 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.providers.grizzly.filters.events; - -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.uri.UriComponents; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.filterchain.FilterChainEvent; - -/** - * {@link FilterChainEvent} to initiate CONNECT tunnelling with a proxy server. - * - * @since 2.0 - * @author The Grizzly Team. - */ -public final class TunnelRequestEvent implements FilterChainEvent { - - private final FilterChainContext suspendedContext; - private final ProxyServer proxyServer; - private final UriComponents uri; - - // ------------------------------------------------------------ Constructors - - public TunnelRequestEvent(final FilterChainContext suspendedContext, final ProxyServer proxyServer, final UriComponents uri) { - this.suspendedContext = suspendedContext; - this.proxyServer = proxyServer; - this.uri = uri; - } - - // ------------------------------------------- Methods from FilterChainEvent - - @Override - public Object type() { - return TunnelRequestEvent.class; - } - - // ---------------------------------------------------------- Public Methods - - public FilterChainContext getSuspendedContext() { - return suspendedContext; - } - - public ProxyServer getProxyServer() { - return proxyServer; - } - - public UriComponents getUri() { - return uri; - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java deleted file mode 100644 index fb12218ba3..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2013 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.providers.grizzly.statushandler; - -import static org.asynchttpclient.providers.grizzly.statushandler.StatusHandler.InvocationStatus.STOP; - -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.providers.grizzly.ConnectionManager; -import org.asynchttpclient.providers.grizzly.HttpTxContext; -import org.asynchttpclient.util.AuthenticatorUtils; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpResponsePacket; -import org.glassfish.grizzly.http.util.Header; -import org.glassfish.grizzly.http.util.HttpStatus; - -import java.security.NoSuchAlgorithmException; -import java.util.Locale; - -public final class AuthorizationHandler implements StatusHandler { - - public static final AuthorizationHandler INSTANCE = new AuthorizationHandler(); - - // ---------------------------------------------- Methods from StatusHandler - - public boolean handlesStatus(int statusCode) { - return (HttpStatus.UNAUTHORIZED_401.statusMatches(statusCode)); - } - - @SuppressWarnings({ "unchecked" }) - public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpTxContext httpTransactionContext, - final FilterChainContext ctx) { - - final String auth = responsePacket.getHeader(Header.WWWAuthenticate); - if (auth == null) { - throw new IllegalStateException("401 response received, but no WWW-Authenticate header was present"); - } - - Realm realm = httpTransactionContext.getRequest().getRealm(); - if (realm == null) { - realm = httpTransactionContext.getProvider().getClientConfig().getRealm(); - } - if (realm == null) { - httpTransactionContext.setInvocationStatus(STOP); - if (httpTransactionContext.getHandler() != null) { - try { - httpTransactionContext.getHandler().onStatusReceived(httpTransactionContext.getResponseStatus()); - } catch (Exception e) { - httpTransactionContext.abort(e); - } - } - return true; - } - - responsePacket.setSkipRemainder(true); // ignore the remainder of the response - - final Request req = httpTransactionContext.getRequest(); - realm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(req.getURI()) - .setMethodName(req.getMethod()).setUsePreemptiveAuth(true).parseWWWAuthenticateHeader(auth).build(); - String lowerCaseAuth = auth.toLowerCase(Locale.ENGLISH); - if (lowerCaseAuth.startsWith("basic")) { - req.getHeaders().remove(Header.Authorization.toString()); - req.getHeaders().add(Header.Authorization.toString(), AuthenticatorUtils.computeBasicAuthentication(realm)); - } else if (lowerCaseAuth.startsWith("digest")) { - req.getHeaders().remove(Header.Authorization.toString()); - try { - req.getHeaders().add(Header.Authorization.toString(), AuthenticatorUtils.computeDigestAuthentication(realm)); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Digest authentication not supported", e); - } - } else { - throw new IllegalStateException("Unsupported authorization method: " + auth); - } - - try { - final Connection c = getConnectionForNextRequest(ctx, req, responsePacket, httpTransactionContext); - final HttpTxContext newContext = httpTransactionContext.copy(); - httpTransactionContext.setFuture(null); - HttpTxContext.set(ctx, newContext); - newContext.setInvocationStatus(STOP); - httpTransactionContext.getProvider().execute(c, req, httpTransactionContext.getHandler(), httpTransactionContext.getFuture(), - newContext); - return false; - } catch (Exception e) { - httpTransactionContext.abort(e); - } - httpTransactionContext.setInvocationStatus(STOP); - return false; - } - - // --------------------------------------------------------- Private Methods - - private Connection getConnectionForNextRequest(final FilterChainContext ctx, final Request request, final HttpResponsePacket response, - final HttpTxContext httpCtx) throws Exception { - /* - if (response.getProcessingState().isKeepAlive()) { - return ctx.getConnection(); - } else { */ - final ConnectionManager m = httpCtx.getProvider().getConnectionManager(); - return m.obtainConnection(request, httpCtx.getFuture()); - /* } */ - } - -} // END AuthorizationHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java deleted file mode 100644 index 40564b2d2e..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (c) 2013 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.providers.grizzly.statushandler; - -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.providers.grizzly.ConnectionManager; -import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; -import org.asynchttpclient.providers.grizzly.HttpTxContext; -import org.asynchttpclient.util.AuthenticatorUtils; -import org.asynchttpclient.util.Base64; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpResponsePacket; -import org.glassfish.grizzly.http.Method; -import org.glassfish.grizzly.http.util.Header; -import org.glassfish.grizzly.http.util.HttpStatus; -import org.ietf.jgss.GSSContext; -import org.ietf.jgss.GSSException; -import org.ietf.jgss.GSSManager; -import org.ietf.jgss.GSSName; -import org.ietf.jgss.Oid; -import org.slf4j.LoggerFactory; - -import java.security.NoSuchAlgorithmException; -import java.util.Locale; - -public final class ProxyAuthorizationHandler implements StatusHandler { - - public static final ProxyAuthorizationHandler INSTANCE = new ProxyAuthorizationHandler(); - - // ---------------------------------------------- Methods from StatusHandler - - public boolean handlesStatus(int statusCode) { - return (HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407.statusMatches(statusCode)); - } - - public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpTxContext httpTransactionContext, - final FilterChainContext ctx) { - - final String proxyAuth = responsePacket.getHeader(Header.ProxyAuthenticate); - if (proxyAuth == null) { - throw new IllegalStateException("407 response received, but no Proxy Authenticate header was present"); - } - - final Request req = httpTransactionContext.getRequest(); - ProxyServer proxyServer = httpTransactionContext.getProvider().getClientConfig().getProxyServerSelector() - .select(req.getURI()); - String principal = proxyServer.getPrincipal(); - String password = proxyServer.getPassword(); - Realm realm = new Realm.RealmBuilder().setPrincipal(principal).setPassword(password).setUri(req.getURI()).setOmitQuery(true) - .setMethodName(Method.CONNECT.getMethodString()).setUsePreemptiveAuth(true).parseProxyAuthenticateHeader(proxyAuth).build(); - String proxyAuthLowerCase = proxyAuth.toLowerCase(Locale.ENGLISH); - if (proxyAuthLowerCase.startsWith("basic")) { - req.getHeaders().remove(Header.ProxyAuthenticate.toString()); - req.getHeaders().remove(Header.ProxyAuthorization.toString()); - req.getHeaders().add(Header.ProxyAuthorization.toString(), AuthenticatorUtils.computeBasicAuthentication(realm)); - } else if (proxyAuthLowerCase.startsWith("digest")) { - req.getHeaders().remove(Header.ProxyAuthenticate.toString()); - req.getHeaders().remove(Header.ProxyAuthorization.toString()); - try { - req.getHeaders().add(Header.ProxyAuthorization.toString(), AuthenticatorUtils.computeDigestAuthentication(realm)); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Digest authentication not supported", e); - } - } else if (proxyAuthLowerCase.startsWith("ntlm")) { - - req.getHeaders().remove(Header.ProxyAuthenticate.toString()); - req.getHeaders().remove(Header.ProxyAuthorization.toString()); - - String msg; - try { - if (isNTLMFirstHandShake(proxyAuth)) { - msg = GrizzlyAsyncHttpProvider.NTLM_ENGINE.generateType1Msg(proxyServer.getNtlmDomain(), ""); - } else { - String serverChallenge = proxyAuth.trim().substring("NTLM ".length()); - msg = GrizzlyAsyncHttpProvider.NTLM_ENGINE.generateType3Msg(principal, password, proxyServer.getNtlmDomain(), - proxyServer.getHost(), serverChallenge); - } - - req.getHeaders().add(Header.ProxyAuthorization.toString(), "NTLM " + msg); - } catch (Exception e1) { - e1.printStackTrace(); - } - } else if (proxyAuthLowerCase.startsWith("negotiate")) { - //this is for kerberos - req.getHeaders().remove(Header.ProxyAuthenticate.toString()); - req.getHeaders().remove(Header.ProxyAuthorization.toString()); - } else { - throw new IllegalStateException("Unsupported authorization method: " + proxyAuth); - } - - InvocationStatus tempInvocationStatus = InvocationStatus.STOP; - - try { - if (isNTLMFirstHandShake(proxyAuth)) { - tempInvocationStatus = InvocationStatus.CONTINUE; - } - if (proxyAuth.toLowerCase().startsWith("negotiate")) { - final Connection c = getConnectionForNextRequest(ctx, req, responsePacket, httpTransactionContext); - final HttpTxContext newContext = httpTransactionContext.copy(); - httpTransactionContext.setFuture(null); - HttpTxContext.set(ctx, newContext); - - newContext.setInvocationStatus(tempInvocationStatus); - - String challengeHeader; - String server = proxyServer.getHost(); - - challengeHeader = GSSSPNEGOWrapper.generateToken(server); - - req.getHeaders().add(Header.ProxyAuthorization.toString(), "Negotiate " + challengeHeader); - - return executeRequest(httpTransactionContext, req, c, newContext); - } else if (isNTLMSecondHandShake(proxyAuth)) { - final Connection c = ctx.getConnection(); - final HttpTxContext newContext = httpTransactionContext.copy(); - - httpTransactionContext.setFuture(null); - HttpTxContext.set(ctx, newContext); - - newContext.setInvocationStatus(tempInvocationStatus); - - return executeRequest(httpTransactionContext, req, c, newContext); - - } else { - final Connection c = getConnectionForNextRequest(ctx, req, responsePacket, httpTransactionContext); - final HttpTxContext newContext = httpTransactionContext.copy(); - httpTransactionContext.setFuture(null); - HttpTxContext.set(ctx, newContext); - - newContext.setInvocationStatus(tempInvocationStatus); - - //NTLM needs the same connection to be used for exchange of tokens - return executeRequest(httpTransactionContext, req, c, newContext); - } - } catch (Exception e) { - httpTransactionContext.abort(e); - } - httpTransactionContext.setInvocationStatus(tempInvocationStatus); - return false; - } - - private boolean executeRequest(final HttpTxContext httpTransactionContext, final Request req, final Connection c, - final HttpTxContext httpTxContext) { - httpTransactionContext.getProvider().execute(c, req, httpTransactionContext.getHandler(), httpTransactionContext.getFuture(), - httpTxContext); - return false; - } - - public static boolean isNTLMSecondHandShake(final String proxyAuth) { - return (proxyAuth != null && proxyAuth.toLowerCase(Locale.ENGLISH).startsWith("ntlm") && !proxyAuth.equalsIgnoreCase("ntlm")); - } - - private static boolean isNTLMFirstHandShake(final String proxy_auth) { - return (proxy_auth.equalsIgnoreCase("ntlm")); - } - - private Connection getConnectionForNextRequest(final FilterChainContext ctx, final Request request, final HttpResponsePacket response, - final HttpTxContext httpCtx) throws Exception { - /* - if (response.getProcessingState().isKeepAlive()) { - return ctx.getConnection(); - } else { */ - final ConnectionManager m = httpCtx.getProvider().getConnectionManager(); - return m.obtainConnection(request, httpCtx.getFuture()); - /* } */ - } - - private static final class GSSSPNEGOWrapper { - private final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(GSSSPNEGOWrapper.class); - private static final String KERBEROS_OID = "1.2.840.113554.1.2.2"; - - static GSSManager getManager() { - return GSSManager.getInstance(); - } - - static byte[] generateGSSToken(final byte[] input, final Oid oid, final String authServer) throws GSSException { - byte[] token = input; - if (token == null) { - token = new byte[0]; - } - GSSManager manager = getManager(); - GSSName serverName = manager.createName("HTTP@" + authServer, GSSName.NT_HOSTBASED_SERVICE); - GSSContext gssContext = manager.createContext(serverName.canonicalize(oid), oid, null, GSSContext.DEFAULT_LIFETIME); - gssContext.requestMutualAuth(true); - gssContext.requestCredDeleg(true); - return gssContext.initSecContext(token, 0, token.length); - } - - public static String generateToken(String authServer) { - String returnVal = ""; - Oid oid; - try { - oid = new Oid(KERBEROS_OID); - byte[] token = GSSSPNEGOWrapper.generateGSSToken(null, oid, authServer); - returnVal = Base64.encode(token); - } catch (GSSException e) { - LOGGER.warn(e.toString(), e); - } - - return returnVal; - } - } -} // END AuthorizationHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/RedirectHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/RedirectHandler.java deleted file mode 100644 index d7572e2ace..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/RedirectHandler.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly.statushandler; - -import static org.asynchttpclient.providers.grizzly.statushandler.StatusHandler.InvocationStatus.CONTINUE; - -import org.asynchttpclient.Request; -import org.asynchttpclient.providers.grizzly.ConnectionManager; -import org.asynchttpclient.providers.grizzly.EventHandler; -import org.asynchttpclient.providers.grizzly.HttpTxContext; -import org.asynchttpclient.uri.UriComponents; -import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpResponsePacket; -import org.glassfish.grizzly.http.util.Header; - -public final class RedirectHandler implements StatusHandler { - - public static final RedirectHandler INSTANCE = new RedirectHandler(); - - // ------------------------------------------ Methods from StatusHandler - - public boolean handlesStatus(int statusCode) { - return (EventHandler.isRedirect(statusCode)); - } - - @SuppressWarnings({ "unchecked" }) - public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpTxContext httpTransactionContext, - final FilterChainContext ctx) { - - final String redirectURL = responsePacket.getHeader(Header.Location); - if (redirectURL == null) { - throw new IllegalStateException("redirect received, but no location header was present"); - } - - UriComponents orig; - if (httpTransactionContext.getLastRedirectURI() == null) { - orig = httpTransactionContext.getRequest().getURI(); - } else { - orig = UriComponents.create(httpTransactionContext.getRequest().getURI(), - httpTransactionContext.getLastRedirectURI()); - } - httpTransactionContext.setLastRedirectURI(redirectURL); - Request requestToSend; - UriComponents uri = UriComponents.create(orig, redirectURL); - if (!uri.toUrl().equalsIgnoreCase(orig.toUrl())) { - requestToSend = EventHandler.newRequest(uri, responsePacket, httpTransactionContext, - sendAsGet(responsePacket, httpTransactionContext)); - } else { - httpTransactionContext.setStatusHandler(null); - httpTransactionContext.setInvocationStatus(CONTINUE); - try { - httpTransactionContext.getHandler().onStatusReceived(httpTransactionContext.getResponseStatus()); - } catch (Exception e) { - httpTransactionContext.abort(e); - } - return true; - } - - final ConnectionManager m = httpTransactionContext.getProvider().getConnectionManager(); - try { - final Connection c = m.obtainConnection(requestToSend, httpTransactionContext.getFuture()); - final HttpTxContext newContext = httpTransactionContext.copy(); - httpTransactionContext.setFuture(null); - newContext.setInvocationStatus(CONTINUE); - newContext.setRequest(requestToSend); - newContext.setRequestUri(requestToSend.getURI()); - HttpTxContext.set(ctx, newContext); - httpTransactionContext.getProvider().execute(c, requestToSend, newContext.getHandler(), newContext.getFuture(), newContext); - return false; - } catch (Exception e) { - httpTransactionContext.abort(e); - } - - httpTransactionContext.setInvocationStatus(CONTINUE); - return true; - - } - - // ------------------------------------------------- Private Methods - - private boolean sendAsGet(final HttpResponsePacket response, final HttpTxContext ctx) { - final int statusCode = response.getStatus(); - return !(statusCode < 302 || statusCode > 303) && - !(statusCode == 302 && ctx.getProvider().getClientConfig().isStrict302Handling()); - } - -} // END RedirectHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/StatusHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/StatusHandler.java deleted file mode 100644 index bca0d2a26f..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/StatusHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2013 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.providers.grizzly.statushandler; - -import org.asynchttpclient.providers.grizzly.HttpTxContext; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpResponsePacket; - -public interface StatusHandler { - - public enum InvocationStatus { - CONTINUE, STOP - } - - boolean handleStatus(final HttpResponsePacket httpResponse, final HttpTxContext httpTransactionContext, final FilterChainContext ctx); - - boolean handlesStatus(final int statusCode); -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/AHCWebSocketListenerAdapter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/AHCWebSocketListenerAdapter.java deleted file mode 100644 index 1d2dd1291c..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/AHCWebSocketListenerAdapter.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) 2013 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.providers.grizzly.websocket; - -import org.asynchttpclient.websocket.WebSocketByteListener; -import org.asynchttpclient.websocket.WebSocketCloseCodeReasonListener; -import org.asynchttpclient.websocket.WebSocketListener; -import org.asynchttpclient.websocket.WebSocketPingListener; -import org.asynchttpclient.websocket.WebSocketPongListener; -import org.asynchttpclient.websocket.WebSocketTextListener; -import org.glassfish.grizzly.websockets.ClosingFrame; -import org.glassfish.grizzly.websockets.DataFrame; - -import java.io.ByteArrayOutputStream; - -final class AHCWebSocketListenerAdapter implements org.glassfish.grizzly.websockets.WebSocketListener { - - private final WebSocketListener ahcListener; - private final GrizzlyWebSocketAdapter webSocket; - private final StringBuilder stringBuffer; - private final ByteArrayOutputStream byteArrayOutputStream; - - // -------------------------------------------------------- Constructors - - public AHCWebSocketListenerAdapter(final WebSocketListener ahcListener, final GrizzlyWebSocketAdapter webSocket) { - this.ahcListener = ahcListener; - this.webSocket = webSocket; - if (webSocket.bufferFragments) { - stringBuffer = new StringBuilder(); - byteArrayOutputStream = new ByteArrayOutputStream(); - } else { - stringBuffer = null; - byteArrayOutputStream = null; - } - } - - // ------------------------------ Methods from Grizzly WebSocketListener - - @Override - public void onClose(org.glassfish.grizzly.websockets.WebSocket gWebSocket, DataFrame dataFrame) { - try { - if (ahcListener instanceof WebSocketCloseCodeReasonListener) { - ClosingFrame cf = ClosingFrame.class.cast(dataFrame); - WebSocketCloseCodeReasonListener.class.cast(ahcListener).onClose(webSocket, cf.getCode(), cf.getReason()); - } else { - ahcListener.onClose(webSocket); - } - } catch (Throwable e) { - ahcListener.onError(e); - } - } - - @Override - public void onConnect(org.glassfish.grizzly.websockets.WebSocket gWebSocket) { - try { - ahcListener.onOpen(webSocket); - } catch (Throwable e) { - ahcListener.onError(e); - } - } - - @Override - public void onMessage(org.glassfish.grizzly.websockets.WebSocket webSocket, String s) { - try { - if (ahcListener instanceof WebSocketTextListener) { - WebSocketTextListener.class.cast(ahcListener).onMessage(s); - } - } catch (Throwable e) { - ahcListener.onError(e); - } - } - - @Override - public void onMessage(org.glassfish.grizzly.websockets.WebSocket webSocket, byte[] bytes) { - try { - if (ahcListener instanceof WebSocketByteListener) { - WebSocketByteListener.class.cast(ahcListener).onMessage(bytes); - } - } catch (Throwable e) { - ahcListener.onError(e); - } - } - - @Override - public void onPing(org.glassfish.grizzly.websockets.WebSocket webSocket, byte[] bytes) { - try { - if (ahcListener instanceof WebSocketPingListener) { - WebSocketPingListener.class.cast(ahcListener).onPing(bytes); - } - } catch (Throwable e) { - ahcListener.onError(e); - } - } - - @Override - public void onPong(org.glassfish.grizzly.websockets.WebSocket webSocket, byte[] bytes) { - try { - if (ahcListener instanceof WebSocketPongListener) { - WebSocketPongListener.class.cast(ahcListener).onPong(bytes); - } - } catch (Throwable e) { - ahcListener.onError(e); - } - } - - @Override - public void onFragment(org.glassfish.grizzly.websockets.WebSocket webSocket, String s, boolean last) { - try { - if (this.webSocket.bufferFragments) { - synchronized (this.webSocket) { - stringBuffer.append(s); - if (last) { - if (ahcListener instanceof WebSocketTextListener) { - final String message = stringBuffer.toString(); - stringBuffer.setLength(0); - WebSocketTextListener.class.cast(ahcListener).onMessage(message); - } - } - } - } - } catch (Throwable e) { - ahcListener.onError(e); - } - } - - @Override - public void onFragment(org.glassfish.grizzly.websockets.WebSocket webSocket, byte[] bytes, boolean last) { - try { - if (this.webSocket.bufferFragments) { - synchronized (this.webSocket) { - byteArrayOutputStream.write(bytes); - if (last) { - if (ahcListener instanceof WebSocketByteListener) { - final byte[] bytesLocal = byteArrayOutputStream.toByteArray(); - byteArrayOutputStream.reset(); - WebSocketByteListener.class.cast(ahcListener).onMessage(bytesLocal); - } - } - } - } - } catch (Throwable e) { - ahcListener.onError(e); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - - AHCWebSocketListenerAdapter that = (AHCWebSocketListenerAdapter) o; - - if (ahcListener != null ? !ahcListener.equals(that.ahcListener) : that.ahcListener != null) - return false; - //noinspection RedundantIfStatement - if (webSocket != null ? !webSocket.equals(that.webSocket) : that.webSocket != null) - return false; - - return true; - } - - @Override - public int hashCode() { - int result = ahcListener != null ? ahcListener.hashCode() : 0; - result = 31 * result + (webSocket != null ? webSocket.hashCode() : 0); - return result; - } -} // END AHCWebSocketListenerAdapter diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyWebSocketAdapter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyWebSocketAdapter.java deleted file mode 100644 index 341c91ffda..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyWebSocketAdapter.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2013 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.providers.grizzly.websocket; - -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.websocket.WebSocket; -import org.asynchttpclient.websocket.WebSocketListener; -import org.glassfish.grizzly.websockets.SimpleWebSocket; - -public final class GrizzlyWebSocketAdapter implements WebSocket { - - private final SimpleWebSocket gWebSocket; - final boolean bufferFragments; - - // -------------------------------------------------------- Constructors - - public GrizzlyWebSocketAdapter(final SimpleWebSocket gWebSocket, final boolean bufferFragments) { - this.gWebSocket = gWebSocket; - this.bufferFragments = bufferFragments; - } - - // ---------------------------------------------- Methods from AHC WebSocket - - @Override - public WebSocket sendMessage(byte[] message) { - gWebSocket.send(message); - return this; - } - - @Override - public WebSocket stream(byte[] fragment, boolean last) { - if (isNonEmpty(fragment)) { - gWebSocket.stream(last, fragment, 0, fragment.length); - } - return this; - } - - @Override - public WebSocket stream(byte[] fragment, int offset, int len, boolean last) { - if (isNonEmpty(fragment)) { - gWebSocket.stream(last, fragment, offset, len); - } - return this; - } - - @Override - public WebSocket sendTextMessage(String message) { - gWebSocket.send(message); - return this; - } - - @Override - public WebSocket streamText(String fragment, boolean last) { - gWebSocket.stream(last, fragment); - return this; - } - - @Override - public WebSocket sendPing(byte[] payload) { - gWebSocket.sendPing(payload); - return this; - } - - @Override - public WebSocket sendPong(byte[] payload) { - gWebSocket.sendPong(payload); - return this; - } - - @Override - public WebSocket addWebSocketListener(WebSocketListener l) { - gWebSocket.add(new AHCWebSocketListenerAdapter(l, this)); - return this; - } - - @Override - public WebSocket removeWebSocketListener(WebSocketListener l) { - gWebSocket.remove(new AHCWebSocketListenerAdapter(l, this)); - return this; - } - - @Override - public boolean isOpen() { - return gWebSocket.isConnected(); - } - - @Override - public void close() { - gWebSocket.close(); - } - - // ---------------------------------------------------------- Public Methods - - public SimpleWebSocket getGrizzlyWebSocket() { - return gWebSocket; - } - -} // END GrizzlyWebSocketAdapter diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncProviderBasicTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncProviderBasicTest.java deleted file mode 100644 index 3c9403e205..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncProviderBasicTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.async.AsyncProvidersBasicTest; -import org.testng.annotations.Test; - -@Test -public class GrizzlyAsyncProviderBasicTest extends AsyncProvidersBasicTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } - - @Override - protected AsyncHttpProviderConfig getProviderConfig() { - final GrizzlyAsyncHttpProviderConfig config = new GrizzlyAsyncHttpProviderConfig(); - return config; - } - - @Override - protected String acceptEncodingHeader() { - return "gzip"; - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncStreamHandlerTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncStreamHandlerTest.java deleted file mode 100644 index 5f4dfd49b2..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncStreamHandlerTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.AsyncStreamHandlerTest; - -public class GrizzlyAsyncStreamHandlerTest extends AsyncStreamHandlerTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncStreamLifecycleTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncStreamLifecycleTest.java deleted file mode 100644 index cee8d596dc..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncStreamLifecycleTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.AsyncStreamLifecycleTest; - -public class GrizzlyAsyncStreamLifecycleTest extends AsyncStreamLifecycleTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAuthTimeoutTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAuthTimeoutTest.java deleted file mode 100644 index 32dd918065..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAuthTimeoutTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.AuthTimeoutTest; - -public class GrizzlyAuthTimeoutTest extends AuthTimeoutTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBasicAuthTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBasicAuthTest.java deleted file mode 100644 index 08387273ae..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBasicAuthTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.BasicAuthTest; - -public class GrizzlyBasicAuthTest extends BasicAuthTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } - - @Override - public String getProviderClass() { - return GrizzlyAsyncHttpProvider.class.getName(); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBasicHttpsTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBasicHttpsTest.java deleted file mode 100644 index c2cedc0eee..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBasicHttpsTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.BasicHttpsTest; - -public class GrizzlyBasicHttpsTest extends BasicHttpsTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBodyChunkTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBodyChunkTest.java deleted file mode 100644 index 782d973081..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBodyChunkTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.BodyChunkTest; - -public class GrizzlyBodyChunkTest extends BodyChunkTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBodyDeferringAsyncHandlerTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBodyDeferringAsyncHandlerTest.java deleted file mode 100644 index 92a2365990..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBodyDeferringAsyncHandlerTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.BodyDeferringAsyncHandlerTest; - -public class GrizzlyBodyDeferringAsyncHandlerTest extends BodyDeferringAsyncHandlerTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyByteBufferCapacityTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyByteBufferCapacityTest.java deleted file mode 100644 index 3151f436ec..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyByteBufferCapacityTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ByteBufferCapacityTest; -import org.testng.annotations.Test; - -public class GrizzlyByteBufferCapacityTest extends ByteBufferCapacityTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void basicByteBufferTest() throws Exception { - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyChunkingTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyChunkingTest.java deleted file mode 100644 index eb20e31ab4..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyChunkingTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ChunkingTest; - -public class GrizzlyChunkingTest extends ChunkingTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyComplexClientTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyComplexClientTest.java deleted file mode 100644 index 4385eeb3ab..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyComplexClientTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ComplexClientTest; - -public class GrizzlyComplexClientTest extends ComplexClientTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyConnectionPoolTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyConnectionPoolTest.java deleted file mode 100644 index be0aed7b7f..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyConnectionPoolTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.asynchttpclient.async.ConnectionPoolTest; -import org.testng.annotations.Test; - -import java.util.concurrent.TimeUnit; - -public class GrizzlyConnectionPoolTest extends ConnectionPoolTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } - - @Override - @Test(enabled = false) - public void testMaxTotalConnectionsException() { - } - - @Override - @Test - public void multipleMaxConnectionOpenTest() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setConnectionTimeout(5000) - .setMaxConnections(1).build(); - AsyncHttpClient c = getAsyncHttpClient(cg); - try { - String body = "hello there"; - - // once - Response response = c.preparePost(getTargetUrl()).setBody(body).execute().get(TIMEOUT, TimeUnit.SECONDS); - - assertEquals(response.getResponseBody(), body); - - // twice - Exception exception = null; - try { - c.preparePost(String.format("http://127.0.0.1:%d/foo/test", port2)).setBody(body).execute().get(TIMEOUT, TimeUnit.SECONDS); - fail("Should throw exception. Too many connections issued."); - } catch (Exception ex) { - ex.printStackTrace(); - exception = ex; - } - assertNotNull(exception); - } finally { - c.close(); - } - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyDigestAuthTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyDigestAuthTest.java deleted file mode 100644 index ab5c9b9010..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyDigestAuthTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.DigestAuthTest; - -public class GrizzlyDigestAuthTest extends DigestAuthTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyEmptyBodyTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyEmptyBodyTest.java deleted file mode 100644 index 58cd22bd20..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyEmptyBodyTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.EmptyBodyTest; - -public class GrizzlyEmptyBodyTest extends EmptyBodyTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyErrorResponseTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyErrorResponseTest.java deleted file mode 100644 index a3f52b8276..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyErrorResponseTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ErrorResponseTest; - -public class GrizzlyErrorResponseTest extends ErrorResponseTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyExpectContinue100Test.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyExpectContinue100Test.java deleted file mode 100644 index fe1f7cff13..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyExpectContinue100Test.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.Expect100ContinueTest; - -public class GrizzlyExpectContinue100Test extends Expect100ContinueTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java deleted file mode 100644 index 2beca1a78f..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java +++ /dev/null @@ -1,437 +0,0 @@ -/* - * Copyright (c) 2013-2014 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.providers.grizzly; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Random; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.DefaultAsyncHttpClient; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.providers.grizzly.FeedableBodyGenerator.NonBlockingFeeder; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.http.server.HttpHandler; -import org.glassfish.grizzly.http.server.HttpServer; -import org.glassfish.grizzly.http.server.NetworkListener; -import static org.glassfish.grizzly.http.server.NetworkListener.DEFAULT_NETWORK_HOST; -import org.glassfish.grizzly.http.server.Request; -import org.glassfish.grizzly.http.server.Response; -import org.glassfish.grizzly.memory.Buffers; -import static org.glassfish.grizzly.memory.MemoryManager.DEFAULT_MEMORY_MANAGER; -import org.glassfish.grizzly.ssl.SSLContextConfigurator; -import org.glassfish.grizzly.ssl.SSLEngineConfigurator; -import org.glassfish.grizzly.utils.Charsets; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.fail; -import static org.testng.AssertJUnit.assertEquals; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -public class GrizzlyFeedableBodyGeneratorTest { - - private static final byte[] DATA = - "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ".getBytes(Charsets.ASCII_CHARSET); - private static final int TEMP_FILE_SIZE = 2 * 1024 * 1024; - private static final int NON_SECURE_PORT = 9991; - private static final int SECURE_PORT = 9992; - - - private HttpServer server; - private File tempFile; - - - // ------------------------------------------------------------------- Setup - - - @BeforeMethod - public void setup() throws Exception { - generateTempFile(); - server = new HttpServer(); - NetworkListener nonSecure = - new NetworkListener("nonsecure", - DEFAULT_NETWORK_HOST, - NON_SECURE_PORT); - NetworkListener secure = - new NetworkListener("secure", - DEFAULT_NETWORK_HOST, - SECURE_PORT); - secure.setSecure(true); - secure.setSSLEngineConfig(createSSLConfig()); - server.addListener(nonSecure); - server.addListener(secure); - server.getServerConfiguration().addHttpHandler(new ConsumingHandler(), "/test"); - server.start(); - } - - - // --------------------------------------------------------------- Tear Down - - - @AfterMethod - public void tearDown() { - if (!tempFile.delete()) { - tempFile.deleteOnExit(); - } - tempFile = null; - server.shutdownNow(); - server = null; - } - - - // ------------------------------------------------------------ Test Methods - - - @Test - public void testSimpleFeederMultipleThreads() throws Exception { - doSimpleFeeder(false); - } - - @Test - public void testSimpleFeederOverSSLMultipleThreads() throws Exception { - doSimpleFeeder(true); - } - - @Test - public void testNonBlockingFeederMultipleThreads() throws Exception { - doNonBlockingFeeder(false); - } - - @Test - public void testNonBlockingFeederOverSSLMultipleThreads() throws Exception { - doNonBlockingFeeder(true); - } - - // --------------------------------------------------------- Private Methods - - - private void doSimpleFeeder(final boolean secure) { - final int threadCount = 10; - final CountDownLatch latch = new CountDownLatch(threadCount); - final int port = (secure ? SECURE_PORT : NON_SECURE_PORT); - final String scheme = (secure ? "https" : "http"); - ExecutorService service = Executors.newFixedThreadPool(threadCount); - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() - .setMaxConnectionsPerHost(60) - .setMaxConnections(60) - .setAcceptAnyCertificate(true) - .build(); - final AsyncHttpClient client = - new DefaultAsyncHttpClient(new GrizzlyAsyncHttpProvider(config), config); - final int[] statusCodes = new int[threadCount]; - final int[] totalsReceived = new int[threadCount]; - final Throwable[] errors = new Throwable[threadCount]; - for (int i = 0; i < threadCount; i++) { - final int idx = i; - service.execute(new Runnable() { - @Override - public void run() { - FeedableBodyGenerator generator = - new FeedableBodyGenerator(); - FeedableBodyGenerator.SimpleFeeder simpleFeeder = - new FeedableBodyGenerator.SimpleFeeder(generator) { - @Override - public void flush() throws IOException { - FileInputStream in = null; - try { - final byte[] bytesIn = new byte[2048]; - in = new FileInputStream(tempFile); - int read; - while ((read = in.read(bytesIn)) != -1) { - final Buffer b = - Buffers.wrap( - DEFAULT_MEMORY_MANAGER, - bytesIn, - 0, - read); - feed(b, false); - } - feed(Buffers.EMPTY_BUFFER, true); - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException ignored) { - } - } - } - } - }; - generator.setFeeder(simpleFeeder); - generator.setMaxPendingBytes(10000); - - RequestBuilder builder = new RequestBuilder("POST"); - builder.setUrl(scheme + "://localhost:" + port + "/test"); - builder.setBody(generator); - try { - client.executeRequest(builder.build(), - new AsyncCompletionHandler() { - @Override - public org.asynchttpclient.Response onCompleted(org.asynchttpclient.Response response) - throws Exception { - try { - totalsReceived[idx] = Integer.parseInt(response.getHeader("x-total")); - } catch (Exception e) { - errors[idx] = e; - } - statusCodes[idx] = response.getStatusCode(); - latch.countDown(); - return response; - } - - @Override - public void onThrowable(Throwable t) { - errors[idx] = t; - t.printStackTrace(); - latch.countDown(); - } - }); - } catch (IOException e) { - errors[idx] = e; - latch.countDown(); - } - } - }); - } - - try { - latch.await(1, TimeUnit.MINUTES); - } catch (InterruptedException e) { - fail("Latch interrupted"); - } - - for (int i = 0; i < threadCount; i++) { - assertEquals(200, statusCodes[i]); - assertNull(errors[i]); - assertEquals(tempFile.length(), totalsReceived[i]); - } - } - - private void doNonBlockingFeeder(final boolean secure) { - final int threadCount = 10; - final CountDownLatch latch = new CountDownLatch(threadCount); - final int port = (secure ? SECURE_PORT : NON_SECURE_PORT); - final String scheme = (secure ? "https" : "http"); - final ExecutorService service = Executors.newCachedThreadPool(); - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() - .setMaxConnectionsPerHost(60) - .setMaxConnections(60) - .setAcceptAnyCertificate(true) - .build(); - final AsyncHttpClient client = - new DefaultAsyncHttpClient(new GrizzlyAsyncHttpProvider(config), config); - final int[] statusCodes = new int[threadCount]; - final int[] totalsReceived = new int[threadCount]; - final Throwable[] errors = new Throwable[threadCount]; - for (int i = 0; i < threadCount; i++) { - final int idx = i; - service.execute(new Runnable() { - @Override - public void run() { - FeedableBodyGenerator generator = - new FeedableBodyGenerator(); - FeedableBodyGenerator.NonBlockingFeeder nonBlockingFeeder = - new FeedableBodyGenerator.NonBlockingFeeder(generator) { - private final Random r = new Random(); - private final InputStream in; - private final byte[] bytesIn = new byte[2048]; - private boolean isDone; - - { - try { - in = new FileInputStream(tempFile); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - - @Override - public void canFeed() throws IOException { - final int read = in.read(bytesIn); - if (read == -1) { - isDone = true; - feed(Buffers.EMPTY_BUFFER, true); - return; - } - - final Buffer b = - Buffers.wrap( - DEFAULT_MEMORY_MANAGER, - bytesIn, - 0, - read); - feed(b, false); - } - - @Override - public boolean isDone() { - return isDone; - } - - @Override - public boolean isReady() { - // simulate real-life usecase, where data could not be ready - return r.nextInt(100) < 80; - } - - @Override - public void notifyReadyToFeed( - final NonBlockingFeeder.ReadyToFeedListener listener) { - service.execute(new Runnable() { - - public void run() { - try { - Thread.sleep(2); - } catch (InterruptedException e) { - } - - listener.ready(); - } - - }); - } - }; - generator.setFeeder(nonBlockingFeeder); - generator.setMaxPendingBytes(10000); - - RequestBuilder builder = new RequestBuilder("POST"); - builder.setUrl(scheme + "://localhost:" + port + "/test"); - builder.setBody(generator); - try { - client.executeRequest(builder.build(), - new AsyncCompletionHandler() { - @Override - public org.asynchttpclient.Response onCompleted(org.asynchttpclient.Response response) - throws Exception { - try { - totalsReceived[idx] = Integer.parseInt(response.getHeader("x-total")); - } catch (Exception e) { - errors[idx] = e; - } - statusCodes[idx] = response.getStatusCode(); - latch.countDown(); - return response; - } - - @Override - public void onThrowable(Throwable t) { - errors[idx] = t; - t.printStackTrace(); - latch.countDown(); - } - }); - } catch (IOException e) { - errors[idx] = e; - latch.countDown(); - } - } - }); - } - - try { - latch.await(1, TimeUnit.MINUTES); - } catch (InterruptedException e) { - fail("Latch interrupted"); - } finally { - service.shutdownNow(); - } - - for (int i = 0; i < threadCount; i++) { - assertEquals(200, statusCodes[i]); - assertNull(errors[i]); - assertEquals(tempFile.length(), totalsReceived[i]); - } - } - - private static SSLEngineConfigurator createSSLConfig() - throws Exception { - final SSLContextConfigurator sslContextConfigurator = - new SSLContextConfigurator(); - final ClassLoader cl = GrizzlyFeedableBodyGeneratorTest.class.getClassLoader(); - // override system properties - final URL cacertsUrl = cl.getResource("ssltest-cacerts.jks"); - if (cacertsUrl != null) { - sslContextConfigurator.setTrustStoreFile(cacertsUrl.getFile()); - sslContextConfigurator.setTrustStorePass("changeit"); - } - - // override system properties - final URL keystoreUrl = cl.getResource("ssltest-keystore.jks"); - if (keystoreUrl != null) { - sslContextConfigurator.setKeyStoreFile(keystoreUrl.getFile()); - sslContextConfigurator.setKeyStorePass("changeit"); - } - - return new SSLEngineConfigurator( - sslContextConfigurator.createSSLContext(), - false, false, false); - } - - - private void generateTempFile() throws IOException { - tempFile = File.createTempFile("feedable", null); - int total = 0; - byte[] chunk = new byte[1024]; - Random r = new Random(System.currentTimeMillis()); - FileOutputStream out = new FileOutputStream(tempFile); - while (total < TEMP_FILE_SIZE) { - for (int i = 0; i < chunk.length; i++) { - chunk[i] = DATA[r.nextInt(DATA.length)]; - } - out.write(chunk); - total += chunk.length; - } - out.flush(); - out.close(); - } - - - // ---------------------------------------------------------- Nested Classes - - - private static final class ConsumingHandler extends HttpHandler { - - - // -------------------------------------------- Methods from HttpHandler - - - @Override - public void service(Request request, Response response) - throws Exception { - int total = 0; - byte[] bytesIn = new byte[2048]; - InputStream in = request.getInputStream(); - int read; - while ((read = in.read(bytesIn)) != -1) { - total += read; - Thread.sleep(5); - } - response.addHeader("X-Total", Integer.toString(total)); - } - - } // END ConsumingHandler - -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFilterTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFilterTest.java deleted file mode 100644 index b45f00a311..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFilterTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.FilterTest; - -public class GrizzlyFilterTest extends FilterTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFollowingThreadTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFollowingThreadTest.java deleted file mode 100644 index 4f3c803611..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFollowingThreadTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.FollowingThreadTest; - -public class GrizzlyFollowingThreadTest extends FollowingThreadTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyHead302Test.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyHead302Test.java deleted file mode 100644 index af3f92b7dc..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyHead302Test.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.Head302Test; - -public class GrizzlyHead302Test extends Head302Test { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyHostnameVerifierTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyHostnameVerifierTest.java deleted file mode 100644 index 7453f6a248..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyHostnameVerifierTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2013 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.HostnameVerifierTest; - -public class GrizzlyHostnameVerifierTest extends HostnameVerifierTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyHttpToHttpsRedirectTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyHttpToHttpsRedirectTest.java deleted file mode 100644 index bd14ab34b4..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyHttpToHttpsRedirectTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.HttpToHttpsRedirectTest; - -public class GrizzlyHttpToHttpsRedirectTest extends HttpToHttpsRedirectTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyIdleStateHandlerTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyIdleStateHandlerTest.java deleted file mode 100644 index adc6a16410..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyIdleStateHandlerTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.IdleStateHandlerTest; - -public class GrizzlyIdleStateHandlerTest extends IdleStateHandlerTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyInputStreamTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyInputStreamTest.java deleted file mode 100644 index 60503e83c3..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyInputStreamTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.InputStreamTest; - -public class GrizzlyInputStreamTest extends InputStreamTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyListenableFutureTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyListenableFutureTest.java deleted file mode 100644 index 866a09729a..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyListenableFutureTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ListenableFutureTest; - -public class GrizzlyListenableFutureTest extends ListenableFutureTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyMaxConnectionsInThreadsTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyMaxConnectionsInThreadsTest.java deleted file mode 100644 index f07f7abfef..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyMaxConnectionsInThreadsTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.MaxConnectionsInThreads; -import org.testng.annotations.Test; - -public class GrizzlyMaxConnectionsInThreadsTest extends MaxConnectionsInThreads { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } - - @Override - @Test(enabled = false) - public void testMaxConnectionsWithinThreads() { - super.testMaxConnectionsWithinThreads(); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyMaxTotalConnectionTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyMaxTotalConnectionTest.java deleted file mode 100644 index f5593bd815..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyMaxTotalConnectionTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.MaxTotalConnectionTest; - -public class GrizzlyMaxTotalConnectionTest extends MaxTotalConnectionTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyMultipleHeaderTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyMultipleHeaderTest.java deleted file mode 100644 index 3cf5965762..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyMultipleHeaderTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.MultipleHeaderTest; - -public class GrizzlyMultipleHeaderTest extends MultipleHeaderTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoNullResponseTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoNullResponseTest.java deleted file mode 100644 index 3dfe5a9354..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoNullResponseTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.NoNullResponseTest; - -public class GrizzlyNoNullResponseTest extends NoNullResponseTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java deleted file mode 100644 index 116a186459..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2014 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.providers.grizzly; - -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.DefaultAsyncHttpClient; -import org.glassfish.grizzly.http.server.HttpHandler; -import org.glassfish.grizzly.http.server.HttpServer; -import org.glassfish.grizzly.http.server.NetworkListener; -import static org.glassfish.grizzly.http.server.NetworkListener.DEFAULT_NETWORK_HOST; -import org.glassfish.grizzly.http.server.Request; -import org.glassfish.grizzly.http.server.Response; -import org.testng.Assert; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -public class GrizzlyNoTransferEncodingTest { - private static final String TEST_MESSAGE = "Hello World!"; - - private HttpServer server; - private int port; - // ------------------------------------------------------------------- Setup - - - @BeforeMethod - public void setup() throws Exception { - server = new HttpServer(); - final NetworkListener listener = - new NetworkListener("server", - DEFAULT_NETWORK_HOST, - 0); - // disable chunking - listener.setChunkingEnabled(false); - server.addListener(listener); - server.getServerConfiguration().addHttpHandler( - new HttpHandler() { - - @Override - public void service(final Request request, - final Response response) throws Exception { - response.setContentType("plain/text;charset=\"utf-8\""); - // flush to make sure content-length will be missed - response.flush(); - - response.getWriter().write(TEST_MESSAGE); - } - }, "/test"); - - server.start(); - - port = listener.getPort(); - } - - - // --------------------------------------------------------------- Tear Down - - - @AfterMethod - public void tearDown() { - server.shutdownNow(); - server = null; - } - - - // ------------------------------------------------------------ Test Methods - - - @Test - public void testNoTransferEncoding() throws Exception { - String url = "http://localhost/" + port + "/test"; - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() - .setCompressionEnabled(true) - .setFollowRedirect(false) - .setConnectionTimeout(15000) - .setRequestTimeout(15000) - .setAllowPoolingConnections(false) - .setDisableUrlEncodingForBoundRequests(true) - .setIOThreadMultiplier(2) // 2 is default - .build(); - - AsyncHttpClient client = new DefaultAsyncHttpClient( - new GrizzlyAsyncHttpProvider(config), config); - - try { - Future f = client.prepareGet(url).execute(); - org.asynchttpclient.Response r = f.get(10, TimeUnit.SECONDS); - Assert.assertEquals(TEST_MESSAGE, r.getResponseBody()); - } finally { - client.close(); - } - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNonAsciiContentLengthTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNonAsciiContentLengthTest.java deleted file mode 100644 index 7534360a33..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNonAsciiContentLengthTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.NonAsciiContentLengthTest; - -public class GrizzlyNonAsciiContentLengthTest extends NonAsciiContentLengthTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyParamEncodingTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyParamEncodingTest.java deleted file mode 100644 index c49beec010..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyParamEncodingTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ParamEncodingTest; - -public class GrizzlyParamEncodingTest extends ParamEncodingTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPerRequestRelative302Test.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPerRequestRelative302Test.java deleted file mode 100644 index 9f7aa9797e..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPerRequestRelative302Test.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PerRequestRelative302Test; - -public class GrizzlyPerRequestRelative302Test extends PerRequestRelative302Test { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPerRequestTimeoutTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPerRequestTimeoutTest.java deleted file mode 100644 index 14bccfc9d0..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPerRequestTimeoutTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PerRequestTimeoutTest; - -public class GrizzlyPerRequestTimeoutTest extends PerRequestTimeoutTest { - - @Override - protected void checkTimeoutMessage(String message) { - assertEquals(message, "Timeout exceeded"); - } - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPostRedirectGetTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPostRedirectGetTest.java deleted file mode 100644 index aeab91c4c2..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPostRedirectGetTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PostRedirectGetTest; - -public class GrizzlyPostRedirectGetTest extends PostRedirectGetTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPostWithQSTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPostWithQSTest.java deleted file mode 100644 index 70f67cfc7d..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPostWithQSTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PostWithQSTest; - -public class GrizzlyPostWithQSTest extends PostWithQSTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyProviderUtil.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyProviderUtil.java deleted file mode 100644 index 501eb0ff7a..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyProviderUtil.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.DefaultAsyncHttpClient; - -public class GrizzlyProviderUtil { - - public static AsyncHttpClient grizzlyProvider(AsyncHttpClientConfig config) { - if (config == null) { - config = new AsyncHttpClientConfig.Builder().build(); - } - return new DefaultAsyncHttpClient(new GrizzlyAsyncHttpProvider(config), config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyProxyTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyProxyTest.java deleted file mode 100644 index f3e1deab47..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyProxyTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ProxyTest; - -public class GrizzlyProxyTest extends ProxyTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyProxyTunnelingTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyProxyTunnelingTest.java deleted file mode 100644 index 1828989c4e..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyProxyTunnelingTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2012-2013 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ProxyTunnellingTest; - -public class GrizzlyProxyTunnelingTest extends ProxyTunnellingTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } - - @Override - public String getProviderClass() { - return GrizzlyAsyncHttpProvider.class.getName(); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPutLargeFileTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPutLargeFileTest.java deleted file mode 100644 index a3c3bfd6b7..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyPutLargeFileTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PutLargeFileTest; - -public class GrizzlyPutLargeFileTest extends PutLargeFileTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyQueryParametersTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyQueryParametersTest.java deleted file mode 100644 index 36ad0a8d50..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyQueryParametersTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.QueryParametersTest; - -public class GrizzlyQueryParametersTest extends QueryParametersTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRC10KTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRC10KTest.java deleted file mode 100644 index ef43002b51..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRC10KTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.RC10KTest; - -public class GrizzlyRC10KTest extends RC10KTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRedirectConnectionUsageTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRedirectConnectionUsageTest.java deleted file mode 100644 index b9fbc218a7..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRedirectConnectionUsageTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.RedirectConnectionUsageTest; - -public class GrizzlyRedirectConnectionUsageTest extends RedirectConnectionUsageTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRelative302Test.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRelative302Test.java deleted file mode 100644 index 6b0dffe2ca..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRelative302Test.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.Relative302Test; - -public class GrizzlyRelative302Test extends Relative302Test { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRemoteSiteTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRemoteSiteTest.java deleted file mode 100644 index cdfa5455f3..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRemoteSiteTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.RemoteSiteTest; - -public class GrizzlyRemoteSiteTest extends RemoteSiteTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRetryRequestTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRetryRequestTest.java deleted file mode 100644 index 8f27e132b1..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyRetryRequestTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.RetryRequestTest; - -public class GrizzlyRetryRequestTest extends RetryRequestTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncClientErrorBehaviourTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncClientErrorBehaviourTest.java deleted file mode 100644 index db9a6a1579..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncClientErrorBehaviourTest.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.SimpleAsyncClientErrorBehaviourTest; - -public class GrizzlySimpleAsyncClientErrorBehaviourTest extends SimpleAsyncClientErrorBehaviourTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } - - public String getProviderClass() { - return GrizzlyAsyncHttpProvider.class.getName(); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncHttpClientTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncHttpClientTest.java deleted file mode 100644 index dbd541b93b..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncHttpClientTest.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.SimpleAsyncHttpClientTest; - -public class GrizzlySimpleAsyncHttpClientTest extends SimpleAsyncHttpClientTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } - - public String getProviderClass() { - return GrizzlyAsyncHttpProvider.class.getName(); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyTransferListenerTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyTransferListenerTest.java deleted file mode 100644 index 6173dbab41..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyTransferListenerTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.TransferListenerTest; - -public class GrizzlyTransferListenerTest extends TransferListenerTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyUnexpectingTimeoutTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyUnexpectingTimeoutTest.java deleted file mode 100644 index 3829983aae..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyUnexpectingTimeoutTest.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2012-2013 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.providers.grizzly; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.asynchttpclient.async.AbstractBasicTest; -import org.eclipse.jetty.continuation.Continuation; -import org.eclipse.jetty.continuation.ContinuationSupport; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; - -public class GrizzlyUnexpectingTimeoutTest extends AbstractBasicTest { - - private static final String MSG = "Unauthorized without WWW-Authenticate header"; - - protected String getExpectedTimeoutMessage() { - return "401 response received, but no WWW-Authenticate header was present"; - } - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new ExpectExceptionHandler(); - } - - private class ExpectExceptionHandler extends AbstractHandler { - public void handle(String target, Request baseRequest, HttpServletRequest request, final HttpServletResponse response) - throws IOException, ServletException { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - final Continuation continuation = ContinuationSupport.getContinuation(request); - continuation.suspend(); - new Thread(new Runnable() { - public void run() { - try { - response.getOutputStream().print(MSG); - response.getOutputStream().flush(); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - }).start(); - baseRequest.setHandled(true); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void unexpectingTimeoutTest() throws IOException { - final AtomicInteger counts = new AtomicInteger(); - final int timeout = 100; - - final AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(timeout).build()); - try { - Future responseFuture = client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandler() { - @Override - public Response onCompleted(Response response) throws Exception { - counts.incrementAndGet(); - return response; - } - - @Override - public void onThrowable(Throwable t) { - counts.incrementAndGet(); - super.onThrowable(t); - } - }); - // currently, an exception is expected - // because the grizzly provider would throw IllegalStateException if WWW-Authenticate header doesn't exist with 401 response status. - try { - Response response = responseFuture.get(); - assertNull(response); - } catch (InterruptedException e) { - fail("Interrupted.", e); - } catch (ExecutionException e) { - assertFalse(e.getCause() instanceof TimeoutException); - assertEquals(e.getCause().getMessage(), getExpectedTimeoutMessage()); - } - // wait for timeout again. - try { - Thread.sleep(timeout * 2); - } catch (InterruptedException e) { - fail("Interrupted.", e); - } - // the result should be either onCompleted or onThrowable. - assertEquals(counts.get(), 1, "result should be one"); - } finally { - client.close(); - } - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyByteMessageTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyByteMessageTest.java deleted file mode 100644 index 7a809b1d71..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyByteMessageTest.java +++ /dev/null @@ -1,32 +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.providers.grizzly.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.grizzly.GrizzlyProviderUtil; -import org.asynchttpclient.websocket.ByteMessageTest; -import org.testng.annotations.Test; - -public class GrizzlyByteMessageTest extends ByteMessageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } - - @Test(timeOut = 60000) - @Override - public void echoFragments() throws Exception { - super.echoFragments(); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyCloseCodeReasonMsgTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyCloseCodeReasonMsgTest.java deleted file mode 100644 index 7ff2884f72..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyCloseCodeReasonMsgTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.grizzly.GrizzlyProviderUtil; -import org.asynchttpclient.websocket.CloseCodeReasonMessageTest; - -public class GrizzlyCloseCodeReasonMsgTest extends CloseCodeReasonMessageTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyProxyTunnellingTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyProxyTunnellingTest.java deleted file mode 100644 index 0d864e1fc2..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyProxyTunnellingTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.grizzly.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.grizzly.GrizzlyProviderUtil; -import org.asynchttpclient.websocket.ProxyTunnellingTest; -import org.testng.annotations.Test; - -@Test -public class GrizzlyProxyTunnellingTest extends ProxyTunnellingTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyRedirectTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyRedirectTest.java deleted file mode 100644 index d01f2424dc..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyRedirectTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 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.providers.grizzly.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.grizzly.GrizzlyProviderUtil; -import org.asynchttpclient.websocket.RedirectTest; - -public class GrizzlyRedirectTest extends RedirectTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyTextMessageTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyTextMessageTest.java deleted file mode 100644 index c89bb0b0fb..0000000000 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyTextMessageTest.java +++ /dev/null @@ -1,25 +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.providers.grizzly.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.grizzly.GrizzlyProviderUtil; -import org.asynchttpclient.websocket.ByteMessageTest; - -public class GrizzlyTextMessageTest extends ByteMessageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return GrizzlyProviderUtil.grizzlyProvider(config); - } -} diff --git a/providers/grizzly/src/test/resources/300k.png b/providers/grizzly/src/test/resources/300k.png deleted file mode 100644 index bff4a85989..0000000000 Binary files a/providers/grizzly/src/test/resources/300k.png and /dev/null differ diff --git a/providers/grizzly/src/test/resources/SimpleTextFile.txt b/providers/grizzly/src/test/resources/SimpleTextFile.txt deleted file mode 100644 index 088788f821..0000000000 --- a/providers/grizzly/src/test/resources/SimpleTextFile.txt +++ /dev/null @@ -1 +0,0 @@ -This is a simple test file \ No newline at end of file diff --git a/providers/grizzly/src/test/resources/client.keystore b/providers/grizzly/src/test/resources/client.keystore deleted file mode 100644 index eaf8339f44..0000000000 Binary files a/providers/grizzly/src/test/resources/client.keystore and /dev/null differ diff --git a/providers/grizzly/src/test/resources/gzip.txt.gz b/providers/grizzly/src/test/resources/gzip.txt.gz deleted file mode 100644 index 80aeb98d2b..0000000000 Binary files a/providers/grizzly/src/test/resources/gzip.txt.gz and /dev/null differ diff --git a/providers/grizzly/src/test/resources/logback-test.xml b/providers/grizzly/src/test/resources/logback-test.xml deleted file mode 100644 index 4acf278710..0000000000 --- a/providers/grizzly/src/test/resources/logback-test.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - %d [%thread] %level %logger - %m%n - - - - - - - - - \ No newline at end of file diff --git a/providers/grizzly/src/test/resources/realm.properties b/providers/grizzly/src/test/resources/realm.properties deleted file mode 100644 index bc9faad66a..0000000000 --- a/providers/grizzly/src/test/resources/realm.properties +++ /dev/null @@ -1 +0,0 @@ -user=admin, admin \ No newline at end of file diff --git a/providers/grizzly/src/test/resources/ssltest-cacerts.jks b/providers/grizzly/src/test/resources/ssltest-cacerts.jks deleted file mode 100644 index 207b9646e6..0000000000 Binary files a/providers/grizzly/src/test/resources/ssltest-cacerts.jks and /dev/null differ diff --git a/providers/grizzly/src/test/resources/ssltest-keystore.jks b/providers/grizzly/src/test/resources/ssltest-keystore.jks deleted file mode 100644 index 70267836e8..0000000000 Binary files a/providers/grizzly/src/test/resources/ssltest-keystore.jks and /dev/null differ diff --git a/providers/grizzly/src/test/resources/textfile.txt b/providers/grizzly/src/test/resources/textfile.txt deleted file mode 100644 index 87daee60a9..0000000000 --- a/providers/grizzly/src/test/resources/textfile.txt +++ /dev/null @@ -1 +0,0 @@ -filecontent: hello \ No newline at end of file diff --git a/providers/grizzly/src/test/resources/textfile2.txt b/providers/grizzly/src/test/resources/textfile2.txt deleted file mode 100644 index 6a91fe609c..0000000000 --- a/providers/grizzly/src/test/resources/textfile2.txt +++ /dev/null @@ -1 +0,0 @@ -filecontent: hello2 \ No newline at end of file diff --git a/providers/netty/pom.xml b/providers/netty/pom.xml deleted file mode 100644 index 57510c4cfc..0000000000 --- a/providers/netty/pom.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - org.asynchttpclient - async-http-client-providers-parent - 2.0.0-SNAPSHOT - - 4.0.0 - async-http-client-netty-provider - Asynchronous Http Client Netty 4 Provider - - The Async Http Client Netty 4 Provider. - - - - - sonatype-releases - https://oss.sonatype.org/content/repositories/releases - - true - - - false - - - - sonatype-snapshots - https://oss.sonatype.org/content/repositories/snapshots - - false - - - true - - - - - - - io.netty - netty-all - 4.0.23.Final - - - org.javassist - javassist - 3.18.2-GA - - - - diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/Callback.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/Callback.java deleted file mode 100644 index 14328ca42e..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/Callback.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty; - -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; - -public abstract class Callback { - - private final NettyResponseFuture future; - - public Callback(NettyResponseFuture future) { - this.future = future; - } - - abstract public void call() throws Exception; - - public NettyResponseFuture future() { - return future; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/DiscardEvent.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/DiscardEvent.java deleted file mode 100755 index a8590efa73..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/DiscardEvent.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty; - -/** - * Simple marker for stopping publishing bytes. - */ -public enum DiscardEvent { - INSTANCE; -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java deleted file mode 100755 index 9f65646fe5..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty; - -import io.netty.util.HashedWheelTimer; -import io.netty.util.Timer; - -import java.io.IOException; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.Request; -import org.asynchttpclient.providers.netty.channel.ChannelManager; -import org.asynchttpclient.providers.netty.request.NettyRequestSender; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class NettyAsyncHttpProvider implements AsyncHttpProvider { - - private static final Logger LOGGER = LoggerFactory.getLogger(NettyAsyncHttpProvider.class); - - private final NettyAsyncHttpProviderConfig nettyConfig; - private final AtomicBoolean closed = new AtomicBoolean(false); - private final ChannelManager channelManager; - private final NettyRequestSender requestSender; - private final boolean allowStopNettyTimer; - private final Timer nettyTimer; - - public NettyAsyncHttpProvider(AsyncHttpClientConfig config) { - - nettyConfig = config.getAsyncHttpProviderConfig() instanceof NettyAsyncHttpProviderConfig ? // - (NettyAsyncHttpProviderConfig) config.getAsyncHttpProviderConfig() - : new NettyAsyncHttpProviderConfig(); - - allowStopNettyTimer = nettyConfig.getNettyTimer() == null; - nettyTimer = allowStopNettyTimer ? newNettyTimer() : nettyConfig.getNettyTimer(); - - channelManager = new ChannelManager(config, nettyConfig, nettyTimer); - requestSender = new NettyRequestSender(config, nettyConfig, channelManager, nettyTimer, closed); - channelManager.configureBootstraps(requestSender, closed); - } - - private Timer newNettyTimer() { - HashedWheelTimer timer = new HashedWheelTimer(); - timer.start(); - return timer; - } - - @Override - public void close() { - if (closed.compareAndSet(false, true)) { - try { - channelManager.close(); - - if (allowStopNettyTimer) - nettyTimer.stop(); - - } catch (Throwable t) { - LOGGER.warn("Unexpected error on close", t); - } - } - } - - @Override - public ListenableFuture execute(Request request, final AsyncHandler asyncHandler) throws IOException { - return requestSender.sendRequest(request, asyncHandler, null, false); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java deleted file mode 100755 index ce81243a53..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.util.Timer; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.SSLEngineFactory; -import org.asynchttpclient.providers.netty.channel.pool.ChannelPool; -import org.asynchttpclient.providers.netty.response.EagerNettyResponseBodyPart; -import org.asynchttpclient.providers.netty.response.LazyNettyResponseBodyPart; -import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; -import org.asynchttpclient.providers.netty.ws.NettyWebSocket; - -/** - * This class can be used to pass Netty's internal configuration options. See - * Netty documentation for more information. - */ -public class NettyAsyncHttpProviderConfig implements AsyncHttpProviderConfig, Object> { - - private final Map, Object> properties = new HashMap, Object>(); - - /** - * Add a property that will be used when the AsyncHttpClient initialize its - * {@link org.asynchttpclient.AsyncHttpProvider} - * - * @param name the name of the property - * @param value the value of the property - * @return this instance of AsyncHttpProviderConfig - */ - public NettyAsyncHttpProviderConfig addProperty(ChannelOption name, Object value) { - properties.put(name, value); - return this; - } - - @SuppressWarnings("unchecked") - public NettyAsyncHttpProviderConfig addChannelOption(ChannelOption name, T value) { - properties.put((ChannelOption) name, value); - return this; - } - - /** - * Return the value associated with the property's name - * - * @param name - * @return this instance of AsyncHttpProviderConfig - */ - public Object getProperty(ChannelOption name) { - return properties.get(name); - } - - /** - * Remove the value associated with the property's name - * - * @param name - * @return true if removed - */ - public Object removeProperty(ChannelOption name) { - return properties.remove(name); - } - - /** - * Return the curent entry set. - * - * @return a the curent entry set. - */ - public Set, Object>> propertiesSet() { - return properties.entrySet(); - } - - public static interface AdditionalChannelInitializer { - - void initChannel(Channel ch) throws Exception; - } - - public static interface ResponseBodyPartFactory { - - NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last); - } - - public static class EagerResponseBodyPartFactory implements ResponseBodyPartFactory { - - @Override - public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { - return new EagerNettyResponseBodyPart(buf, last); - } - } - - public static class LazyResponseBodyPartFactory implements ResponseBodyPartFactory { - - @Override - public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { - return new LazyNettyResponseBodyPart(buf, last); - } - } - - public static interface NettyWebSocketFactory { - NettyWebSocket newNettyWebSocket(Channel channel, NettyAsyncHttpProviderConfig nettyConfig); - } - - public class DefaultNettyWebSocketFactory implements NettyWebSocketFactory { - - @Override - public NettyWebSocket newNettyWebSocket(Channel channel, NettyAsyncHttpProviderConfig nettyConfig) { - return new NettyWebSocket(channel, nettyConfig); - } - } - - /** - * Allow configuring the Netty's event loop. - */ - private EventLoopGroup eventLoopGroup; - - private AdditionalChannelInitializer httpAdditionalChannelInitializer; - private AdditionalChannelInitializer wsAdditionalChannelInitializer; - private AdditionalChannelInitializer httpsAdditionalChannelInitializer; - private AdditionalChannelInitializer wssAdditionalChannelInitializer; - - /** - * Allow configuring Netty's HttpClientCodecs. - */ - private int httpClientCodecMaxInitialLineLength = 4096; - private int httpClientCodecMaxHeaderSize = 8192; - private int httpClientCodecMaxChunkSize = 8192; - - private ResponseBodyPartFactory bodyPartFactory = new EagerResponseBodyPartFactory(); - - private ChannelPool channelPool; - - /** - * Allow one to disable zero copy for bodies and use chunking instead - */ - private boolean disableZeroCopy; - - private Timer nettyTimer; - - private long handshakeTimeout; - - private SSLEngineFactory sslEngineFactory; - - /** - * chunkedFileChunkSize - */ - private int chunkedFileChunkSize = 8192; - - private NettyWebSocketFactory nettyWebSocketFactory = new DefaultNettyWebSocketFactory(); - - private int webSocketMaxBufferSize = 128000000; - - private int webSocketMaxFrameSize = 10 * 1024; - - public EventLoopGroup getEventLoopGroup() { - return eventLoopGroup; - } - - public void setEventLoopGroup(EventLoopGroup eventLoopGroup) { - this.eventLoopGroup = eventLoopGroup; - } - - public AdditionalChannelInitializer getHttpAdditionalChannelInitializer() { - return httpAdditionalChannelInitializer; - } - - public void setHttpAdditionalChannelInitializer(AdditionalChannelInitializer httpAdditionalChannelInitializer) { - this.httpAdditionalChannelInitializer = httpAdditionalChannelInitializer; - } - - public AdditionalChannelInitializer getWsAdditionalChannelInitializer() { - return wsAdditionalChannelInitializer; - } - - public void setWsAdditionalChannelInitializer(AdditionalChannelInitializer wsAdditionalChannelInitializer) { - this.wsAdditionalChannelInitializer = wsAdditionalChannelInitializer; - } - - public AdditionalChannelInitializer getHttpsAdditionalChannelInitializer() { - return httpsAdditionalChannelInitializer; - } - - public void setHttpsAdditionalChannelInitializer(AdditionalChannelInitializer httpsAdditionalChannelInitializer) { - this.httpsAdditionalChannelInitializer = httpsAdditionalChannelInitializer; - } - - public AdditionalChannelInitializer getWssAdditionalChannelInitializer() { - return wssAdditionalChannelInitializer; - } - - public void setWssAdditionalChannelInitializer(AdditionalChannelInitializer wssAdditionalChannelInitializer) { - this.wssAdditionalChannelInitializer = wssAdditionalChannelInitializer; - } - - public int getHttpClientCodecMaxInitialLineLength() { - return httpClientCodecMaxInitialLineLength; - } - - public void setHttpClientCodecMaxInitialLineLength(int httpClientCodecMaxInitialLineLength) { - this.httpClientCodecMaxInitialLineLength = httpClientCodecMaxInitialLineLength; - } - - public int getHttpClientCodecMaxHeaderSize() { - return httpClientCodecMaxHeaderSize; - } - - public void setHttpClientCodecMaxHeaderSize(int httpClientCodecMaxHeaderSize) { - this.httpClientCodecMaxHeaderSize = httpClientCodecMaxHeaderSize; - } - - public int getHttpClientCodecMaxChunkSize() { - return httpClientCodecMaxChunkSize; - } - - public void setHttpClientCodecMaxChunkSize(int httpClientCodecMaxChunkSize) { - this.httpClientCodecMaxChunkSize = httpClientCodecMaxChunkSize; - } - - public ResponseBodyPartFactory getBodyPartFactory() { - return bodyPartFactory; - } - - public void setBodyPartFactory(ResponseBodyPartFactory bodyPartFactory) { - this.bodyPartFactory = bodyPartFactory; - } - - public ChannelPool getChannelPool() { - return channelPool; - } - - public void setChannelPool(ChannelPool channelPool) { - this.channelPool = channelPool; - } - - public boolean isDisableZeroCopy() { - return disableZeroCopy; - } - - public void setDisableZeroCopy(boolean disableZeroCopy) { - this.disableZeroCopy = disableZeroCopy; - } - - public Timer getNettyTimer() { - return nettyTimer; - } - - public void setNettyTimer(Timer nettyTimer) { - this.nettyTimer = nettyTimer; - } - - public long getHandshakeTimeout() { - return handshakeTimeout; - } - - public void setHandshakeTimeout(long handshakeTimeout) { - this.handshakeTimeout = handshakeTimeout; - } - - public SSLEngineFactory getSslEngineFactory() { - return sslEngineFactory; - } - - public void setSslEngineFactory(SSLEngineFactory sslEngineFactory) { - this.sslEngineFactory = sslEngineFactory; - } - - public int getChunkedFileChunkSize() { - return chunkedFileChunkSize; - } - - public void setChunkedFileChunkSize(int chunkedFileChunkSize) { - this.chunkedFileChunkSize = chunkedFileChunkSize; - } - - public NettyWebSocketFactory getNettyWebSocketFactory() { - return nettyWebSocketFactory; - } - - public void setNettyWebSocketFactory(NettyWebSocketFactory nettyWebSocketFactory) { - this.nettyWebSocketFactory = nettyWebSocketFactory; - } - - public int getWebSocketMaxBufferSize() { - return webSocketMaxBufferSize; - } - - public void setWebSocketMaxBufferSize(int webSocketMaxBufferSize) { - this.webSocketMaxBufferSize = webSocketMaxBufferSize; - } - - public int getWebSocketMaxFrameSize() { - return webSocketMaxFrameSize; - } - public void setWebSocketMaxFrameSize(int webSocketMaxFrameSize) { - this.webSocketMaxFrameSize = webSocketMaxFrameSize; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java deleted file mode 100755 index 2d7153f555..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java +++ /dev/null @@ -1,437 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.channel; - -import static org.asynchttpclient.providers.netty.util.HttpUtils.WEBSOCKET; -import static org.asynchttpclient.providers.netty.util.HttpUtils.isSecure; -import static org.asynchttpclient.providers.netty.util.HttpUtils.isWebSocket; -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.group.ChannelGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.codec.http.HttpClientCodec; -import io.netty.handler.codec.http.HttpContentDecompressor; -import io.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder; -import io.netty.handler.codec.http.websocketx.WebSocket08FrameEncoder; -import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator; -import io.netty.handler.ssl.SslHandler; -import io.netty.handler.stream.ChunkedWriteHandler; -import io.netty.util.Timer; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.Callback; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.providers.netty.channel.pool.ChannelPool; -import org.asynchttpclient.providers.netty.channel.pool.DefaultChannelPool; -import org.asynchttpclient.providers.netty.channel.pool.NoopChannelPool; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.handler.HttpProtocol; -import org.asynchttpclient.providers.netty.handler.Processor; -import org.asynchttpclient.providers.netty.handler.WebSocketProtocol; -import org.asynchttpclient.providers.netty.request.NettyRequestSender; -import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.SslUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ChannelManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(ChannelManager.class); - public static final String HTTP_HANDLER = "httpHandler"; - public static final String SSL_HANDLER = "sslHandler"; - public static final String HTTP_PROCESSOR = "httpProcessor"; - public static final String WS_PROCESSOR = "wsProcessor"; - public static final String DEFLATER_HANDLER = "deflater"; - public static final String INFLATER_HANDLER = "inflater"; - public static final String CHUNKED_WRITER_HANDLER = "chunkedWriter"; - public static final String WS_DECODER_HANDLER = "ws-decoder"; - public static final String WS_FRAME_AGGREGATOR = "ws-aggregator"; - public static final String WS_ENCODER_HANDLER = "ws-encoder"; - - private final AsyncHttpClientConfig config; - private final NettyAsyncHttpProviderConfig nettyConfig; - - private final EventLoopGroup eventLoopGroup; - private final boolean allowReleaseEventLoopGroup; - - private final Bootstrap plainBootstrap; - private final Bootstrap secureBootstrap; - private final Bootstrap webSocketBootstrap; - private final Bootstrap secureWebSocketBootstrap; - - private final long handshakeTimeout; - - private final ChannelPool channelPool; - private final boolean maxConnectionsEnabled; - private final Semaphore freeChannels; - private final ChannelGroup openChannels; - private final boolean maxConnectionsPerHostEnabled; - private final ConcurrentHashMap freeChannelsPerHost; - private final ConcurrentHashMap channel2KeyPool; - - private Processor wsProcessor; - - public ChannelManager(AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, Timer nettyTimer) { - - this.config = config; - this.nettyConfig = nettyConfig; - - ChannelPool channelPool = nettyConfig.getChannelPool(); - if (channelPool == null && config.isAllowPoolingConnections()) { - channelPool = new DefaultChannelPool(config, nettyTimer); - } else if (channelPool == null) { - channelPool = new NoopChannelPool(); - } - this.channelPool = channelPool; - - maxConnectionsEnabled = config.getMaxConnections() > 0; - maxConnectionsPerHostEnabled = config.getMaxConnectionsPerHost() > 0; - - if (maxConnectionsEnabled) { - openChannels = new CleanupChannelGroup("asyncHttpClient") { - @Override - public boolean remove(Object o) { - boolean removed = super.remove(o); - if (removed) { - freeChannels.release(); - if (maxConnectionsPerHostEnabled) { - String poolKey = channel2KeyPool.remove(Channel.class.cast(o)); - if (poolKey != null) { - Semaphore freeChannelsForHost = freeChannelsPerHost.get(poolKey); - if (freeChannelsForHost != null) - freeChannelsForHost.release(); - } - } - } - return removed; - } - }; - freeChannels = new Semaphore(config.getMaxConnections()); - } else { - openChannels = new CleanupChannelGroup("asyncHttpClient"); - freeChannels = null; - } - - if (maxConnectionsPerHostEnabled) { - freeChannelsPerHost = new ConcurrentHashMap(); - channel2KeyPool = new ConcurrentHashMap(); - } else { - freeChannelsPerHost = null; - channel2KeyPool = null; - } - - handshakeTimeout = nettyConfig.getHandshakeTimeout(); - - // check if external EventLoopGroup is defined - allowReleaseEventLoopGroup = nettyConfig.getEventLoopGroup() == null; - eventLoopGroup = allowReleaseEventLoopGroup ? new NioEventLoopGroup() : nettyConfig.getEventLoopGroup(); - if (!(eventLoopGroup instanceof NioEventLoopGroup)) - throw new IllegalArgumentException("Only Nio is supported"); - - plainBootstrap = new Bootstrap().channel(NioSocketChannel.class).group(eventLoopGroup); - secureBootstrap = new Bootstrap().channel(NioSocketChannel.class).group(eventLoopGroup); - webSocketBootstrap = new Bootstrap().channel(NioSocketChannel.class).group(eventLoopGroup); - secureWebSocketBootstrap = new Bootstrap().channel(NioSocketChannel.class).group(eventLoopGroup); - - if (config.getConnectionTimeout() > 0) - nettyConfig.addChannelOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectionTimeout()); - for (Entry, Object> entry : nettyConfig.propertiesSet()) { - ChannelOption key = entry.getKey(); - Object value = entry.getValue(); - plainBootstrap.option(key, value); - webSocketBootstrap.option(key, value); - secureBootstrap.option(key, value); - secureWebSocketBootstrap.option(key, value); - } - } - - public void configureBootstraps(NettyRequestSender requestSender, AtomicBoolean closed) { - - HttpProtocol httpProtocol = new HttpProtocol(this, config, nettyConfig, requestSender); - final Processor httpProcessor = new Processor(config, this, requestSender, httpProtocol); - - WebSocketProtocol wsProtocol = new WebSocketProtocol(this, config, nettyConfig, requestSender); - wsProcessor = new Processor(config, this, requestSender, wsProtocol); - - plainBootstrap.handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ChannelPipeline pipeline = ch.pipeline().addLast(HTTP_HANDLER, newHttpClientCodec()); - - if (config.isCompressionEnabled()) { - pipeline.addLast(INFLATER_HANDLER, new HttpContentDecompressor()); - } - pipeline.addLast(CHUNKED_WRITER_HANDLER, new ChunkedWriteHandler())// - .addLast(HTTP_PROCESSOR, httpProcessor); - - if (nettyConfig.getHttpAdditionalChannelInitializer() != null) { - nettyConfig.getHttpAdditionalChannelInitializer().initChannel(ch); - } - } - }); - - webSocketBootstrap.handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline()// - .addLast(HTTP_HANDLER, newHttpClientCodec())// - .addLast(WS_PROCESSOR, wsProcessor); - - if (nettyConfig.getWsAdditionalChannelInitializer() != null) { - nettyConfig.getWsAdditionalChannelInitializer().initChannel(ch); - } - } - }); - - secureBootstrap.handler(new ChannelInitializer() { - - @Override - protected void initChannel(Channel ch) throws Exception { - - ChannelPipeline pipeline = ch.pipeline()// - .addLast(SSL_HANDLER, new SslInitializer(ChannelManager.this)).addLast(HTTP_HANDLER, newHttpClientCodec()); - - if (config.isCompressionEnabled()) { - pipeline.addLast(INFLATER_HANDLER, new HttpContentDecompressor()); - } - pipeline.addLast(CHUNKED_WRITER_HANDLER, new ChunkedWriteHandler())// - .addLast(HTTP_PROCESSOR, httpProcessor); - - if (nettyConfig.getHttpsAdditionalChannelInitializer() != null) { - nettyConfig.getHttpsAdditionalChannelInitializer().initChannel(ch); - } - } - }); - - secureWebSocketBootstrap.handler(new ChannelInitializer() { - - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline()// - .addLast(SSL_HANDLER, new SslInitializer(ChannelManager.this))// - .addLast(HTTP_HANDLER, newHttpClientCodec())// - .addLast(WS_PROCESSOR, wsProcessor); - - if (nettyConfig.getWssAdditionalChannelInitializer() != null) { - nettyConfig.getWssAdditionalChannelInitializer().initChannel(ch); - } - } - }); - } - - public final void tryToOfferChannelToPool(Channel channel, boolean keepAlive, String poolKey) { - if (keepAlive && channel.isActive()) { - LOGGER.debug("Adding key: {} for channel {}", poolKey, channel); - channelPool.offer(channel, poolKey); - if (maxConnectionsPerHostEnabled) - channel2KeyPool.putIfAbsent(channel, poolKey); - Channels.setDiscard(channel); - } else { - // not offered - closeChannel(channel); - } - } - - public Channel poll(String uri) { - return channelPool.poll(uri); - } - - public boolean removeAll(Channel connection) { - return channelPool.removeAll(connection); - } - - private boolean tryAcquireGlobal() { - return !maxConnectionsEnabled || freeChannels.tryAcquire(); - } - - private Semaphore getFreeConnectionsForHost(String poolKey) { - Semaphore freeConnections = freeChannelsPerHost.get(poolKey); - if (freeConnections == null) { - // lazy create the semaphore - Semaphore newFreeConnections = new Semaphore(config.getMaxConnectionsPerHost()); - freeConnections = freeChannelsPerHost.putIfAbsent(poolKey, newFreeConnections); - if (freeConnections == null) - freeConnections = newFreeConnections; - } - return freeConnections; - } - - private boolean tryAcquirePerHost(String poolKey) { - return !maxConnectionsPerHostEnabled || getFreeConnectionsForHost(poolKey).tryAcquire(); - } - - public boolean preemptChannel(String poolKey) { - return channelPool.isOpen() && tryAcquireGlobal() && tryAcquirePerHost(poolKey); - } - - public void close() { - channelPool.destroy(); - openChannels.close(); - - for (Channel channel : openChannels) { - Object attribute = Channels.getAttribute(channel); - if (attribute instanceof NettyResponseFuture) { - NettyResponseFuture future = (NettyResponseFuture) attribute; - future.cancelTimeouts(); - } - } - - if (allowReleaseEventLoopGroup) - eventLoopGroup.shutdownGracefully(); - } - - public void closeChannel(Channel channel) { - - LOGGER.debug("Closing Channel {} ", channel); - try { - removeAll(channel); - Channels.setDiscard(channel); - channel.close(); - } catch (Throwable t) { - LOGGER.debug("Error closing a connection", t); - } - openChannels.remove(channel); - } - - public void abortChannelPreemption(String poolKey) { - if (maxConnectionsEnabled) - freeChannels.release(); - if (maxConnectionsPerHostEnabled) - getFreeConnectionsForHost(poolKey).release(); - } - - public void registerOpenChannel(Channel channel) { - openChannels.add(channel); - } - - private HttpClientCodec newHttpClientCodec() { - return new HttpClientCodec(// - nettyConfig.getHttpClientCodecMaxInitialLineLength(),// - nettyConfig.getHttpClientCodecMaxHeaderSize(),// - nettyConfig.getHttpClientCodecMaxChunkSize(),// - false); - } - - public SslHandler createSslHandler(String peerHost, int peerPort) throws IOException, GeneralSecurityException { - - SSLEngine sslEngine = null; - if (nettyConfig.getSslEngineFactory() != null) { - sslEngine = nettyConfig.getSslEngineFactory().newSSLEngine(); - - } else { - SSLContext sslContext = config.getSSLContext(); - if (sslContext == null) - sslContext = SslUtils.getInstance().getSSLContext(config.isAcceptAnyCertificate()); - - sslEngine = sslContext.createSSLEngine(peerHost, peerPort); - sslEngine.setUseClientMode(true); - } - - SslHandler sslHandler = new SslHandler(sslEngine); - if (handshakeTimeout > 0) - sslHandler.setHandshakeTimeoutMillis(handshakeTimeout); - - return sslHandler; - } - - public static SslHandler getSslHandler(ChannelPipeline pipeline) { - return (SslHandler) pipeline.get(SSL_HANDLER); - } - - public static boolean isSslHandlerConfigured(ChannelPipeline pipeline) { - return pipeline.get(SSL_HANDLER) != null; - } - - public void upgradeProtocol(ChannelPipeline pipeline, String scheme, String host, int port) throws IOException, GeneralSecurityException { - if (pipeline.get(HTTP_HANDLER) != null) - pipeline.remove(HTTP_HANDLER); - - if (isSecure(scheme)) - if (isSslHandlerConfigured(pipeline)) { - pipeline.addAfter(SSL_HANDLER, HTTP_HANDLER, newHttpClientCodec()); - } else { - pipeline.addFirst(HTTP_HANDLER, newHttpClientCodec()); - pipeline.addFirst(SSL_HANDLER, createSslHandler(host, port)); - } - - else - pipeline.addFirst(HTTP_HANDLER, newHttpClientCodec()); - - if (isWebSocket(scheme)) { - pipeline.addAfter(HTTP_PROCESSOR, WS_PROCESSOR, wsProcessor); - pipeline.remove(HTTP_PROCESSOR); - } - } - - public String getPoolKey(NettyResponseFuture future) { - return future.getConnectionPoolKeyStrategy().getKey(future.getURI(), future.getProxyServer()); - } - - /** - * Always make sure the channel who got cached support the proper protocol. - * It could only occurs when a HttpMethod. CONNECT is used against a proxy - * that requires upgrading from http to https. - */ - public void verifyChannelPipeline(ChannelPipeline pipeline, String scheme) throws IOException, GeneralSecurityException { - - boolean sslHandlerConfigured = isSslHandlerConfigured(pipeline); - - if (isSecure(scheme)) { - if (!sslHandlerConfigured) - pipeline.addFirst(SSL_HANDLER, new SslInitializer(this)); - - } else if (sslHandlerConfigured) - pipeline.remove(SSL_HANDLER); - } - - public Bootstrap getBootstrap(UriComponents uri, boolean useProxy, boolean useSSl) { - return uri.getScheme().startsWith(WEBSOCKET) && !useProxy ? (useSSl ? secureWebSocketBootstrap : webSocketBootstrap) : // - (useSSl ? secureBootstrap : plainBootstrap); - } - - public void upgradePipelineForWebSockets(ChannelPipeline pipeline) { - pipeline.addAfter(HTTP_HANDLER, WS_ENCODER_HANDLER, new WebSocket08FrameEncoder(true)); - pipeline.remove(HTTP_HANDLER); - pipeline.addBefore(WS_PROCESSOR, WS_DECODER_HANDLER, new WebSocket08FrameDecoder(false, false, nettyConfig.getWebSocketMaxFrameSize())); - pipeline.addAfter(WS_DECODER_HANDLER, WS_FRAME_AGGREGATOR, new WebSocketFrameAggregator(nettyConfig.getWebSocketMaxBufferSize())); - } - - public final Callback newDrainCallback(final NettyResponseFuture future, final Channel channel, final boolean keepAlive, final String poolKey) { - - return new Callback(future) { - public void call() throws Exception { - tryToOfferChannelToPool(channel, keepAlive, poolKey); - } - }; - } - - public void drainChannel(final Channel channel, final NettyResponseFuture future) { - Channels.setAttribute(channel, newDrainCallback(future, channel, future.isKeepAlive(), getPoolKey(future))); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java deleted file mode 100755 index a193ae5f01..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.channel; - -import io.netty.channel.Channel; -import io.netty.util.Attribute; -import io.netty.util.AttributeKey; - -import org.asynchttpclient.providers.netty.DiscardEvent; - -public class Channels { - - private static final AttributeKey DEFAULT_ATTRIBUTE = AttributeKey.valueOf("default"); - - public static Object getAttribute(Channel channel) { - Attribute attr = channel.attr(DEFAULT_ATTRIBUTE); - return attr != null ? attr.get() : null; - } - - public static void setAttribute(Channel channel, Object o) { - channel.attr(DEFAULT_ATTRIBUTE).set(o); - } - - public static void setDiscard(Channel channel) { - setAttribute(channel, DiscardEvent.INSTANCE); - } - - public static boolean isChannelValid(Channel channel) { - return channel != null && channel.isOpen() && channel.isActive(); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/CleanupChannelGroup.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/CleanupChannelGroup.java deleted file mode 100755 index edd0c2643a..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/CleanupChannelGroup.java +++ /dev/null @@ -1,101 +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. - */ -/* - * Copyright 2010 Bruno de Carvalho - * - * 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.providers.netty.channel; - -import io.netty.channel.Channel; -import io.netty.channel.group.ChannelGroupFuture; -import io.netty.channel.group.DefaultChannelGroup; -import io.netty.util.concurrent.GlobalEventExecutor; - -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * Extension of {@link DefaultChannelGroup} that's used mainly as a cleanup container, where {@link #close()} is only - * supposed to be called once. - * - * @author Bruno de Carvalho - */ -public class CleanupChannelGroup extends DefaultChannelGroup { - - // internal vars -------------------------------------------------------------------------------------------------- - - private final AtomicBoolean closed = new AtomicBoolean(false); - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - - // constructors --------------------------------------------------------------------------------------------------- - - public CleanupChannelGroup() { - super(GlobalEventExecutor.INSTANCE); - } - - public CleanupChannelGroup(String name) { - super(name, GlobalEventExecutor.INSTANCE); - } - - // DefaultChannelGroup -------------------------------------------------------------------------------------------- - - @Override - public ChannelGroupFuture close() { - this.lock.writeLock().lock(); - try { - if (!this.closed.getAndSet(true)) { - // First time close() is called. - return super.close(); - } else { - // FIXME DefaultChannelGroupFuture is package protected - // Collection futures = new ArrayList(); - // logger.debug("CleanupChannelGroup already closed"); - // return new DefaultChannelGroupFuture(ChannelGroup.class.cast(this), futures, - // GlobalEventExecutor.INSTANCE); - throw new UnsupportedOperationException("CleanupChannelGroup already closed"); - } - } finally { - this.lock.writeLock().unlock(); - } - } - - @Override - public boolean add(Channel channel) { - // Synchronization must occur to avoid add() and close() overlap (thus potentially leaving one channel open). - // This could also be done by synchronizing the method itself but using a read lock here (rather than a - // synchronized() block) allows multiple concurrent calls to add(). - this.lock.readLock().lock(); - try { - if (this.closed.get()) { - // Immediately close channel, as close() was already called. - channel.close(); - return false; - } - - return super.add(channel); - } finally { - this.lock.readLock().unlock(); - } - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java deleted file mode 100755 index 0a938b1f5f..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2014 AsyncHttpClient Project. - * - * Ning licenses this file 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: - * - * 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.providers.netty.channel; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelOutboundHandlerAdapter; -import io.netty.channel.ChannelPromise; -import io.netty.handler.ssl.SslHandler; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; - -/** - * On connect, replaces itself with a SslHandler that has a SSLEngine configured with the remote host and port. - * - * @author slandelle - */ -public class SslInitializer extends ChannelOutboundHandlerAdapter { - - private final ChannelManager channelManager; - - public SslInitializer(ChannelManager channelManager) { - this.channelManager = channelManager; - } - - @Override - public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) - throws Exception { - - InetSocketAddress remoteInetSocketAddress = (InetSocketAddress) remoteAddress; - String peerHost = remoteInetSocketAddress.getHostString(); - int peerPort = remoteInetSocketAddress.getPort(); - - SslHandler sslHandler = channelManager.createSslHandler(peerHost, peerPort); - - ctx.pipeline().replace(ChannelManager.SSL_HANDLER, ChannelManager.SSL_HANDLER, sslHandler); - - ctx.connect(remoteAddress, localAddress, promise); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/ChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/ChannelPool.java deleted file mode 100755 index ef1fbd9973..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/ChannelPool.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.channel.pool; - -import io.netty.channel.Channel; - -public interface ChannelPool { - - /** - * Add a channel to the pool - * - * @param poolKey a key used to retrieve the cached channel - * @param channel an I/O channel - * @return true if added. - */ - boolean offer(Channel channel, String poolKey); - - /** - * Remove the channel associated with the uri. - * - * @param uri the uri used when invoking addConnection - * @return the channel associated with the uri - */ - Channel poll(String uri); - - /** - * Remove all channels from the cache. A channel might have been associated with several uri. - * - * @param channel a channel - * @return the true if the channel has been removed - */ - boolean removeAll(Channel channel); - - /** - * Return true if a channel can be cached. A implementation can decide based on some rules to allow caching - * Calling this method is equivalent of checking the returned value of {@link ChannelPool#offer(Object, Object)} - * - * @return true if a channel can be cached. - */ - boolean isOpen(); - - /** - * Destroy all channels that has been cached by this instance. - */ - void destroy(); -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java deleted file mode 100755 index c82ad5be9a..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.channel.pool; - -import static org.asynchttpclient.util.DateUtils.millisTime; -import io.netty.channel.Channel; -import io.netty.util.Timeout; -import io.netty.util.Timer; -import io.netty.util.TimerTask; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.channel.Channels; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A simple implementation of {@link com.ning.http.client.providers.netty.pool.ChannelPool} based on a {@link java.util.concurrent.ConcurrentHashMap} - */ -public final class DefaultChannelPool implements ChannelPool { - - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultChannelPool.class); - - private final ConcurrentHashMap> poolsPerKey = new ConcurrentHashMap>(); - private final ConcurrentHashMap channel2Creation = new ConcurrentHashMap(); - private final AtomicBoolean isClosed = new AtomicBoolean(false); - private final Timer nettyTimer; - private final boolean sslConnectionPoolEnabled; - private final int maxConnectionTTL; - private final boolean maxConnectionTTLDisabled; - private final long maxIdleTime; - private final boolean maxIdleTimeDisabled; - private final long cleanerPeriod; - - public DefaultChannelPool(AsyncHttpClientConfig config, Timer hashedWheelTimer) { - this(config.getPooledConnectionIdleTimeout(),// - config.getConnectionTTL(),// - config.isAllowPoolingSslConnections(),// - hashedWheelTimer); - } - - public DefaultChannelPool( - long maxIdleTime,// - int maxConnectionTTL,// - boolean sslConnectionPoolEnabled,// - Timer nettyTimer) { - this.sslConnectionPoolEnabled = sslConnectionPoolEnabled; - this.maxIdleTime = maxIdleTime; - this.maxConnectionTTL = maxConnectionTTL; - maxConnectionTTLDisabled = maxConnectionTTL <= 0; - this.nettyTimer = nettyTimer; - maxIdleTimeDisabled = maxIdleTime <= 0; - - cleanerPeriod = Math.min(maxConnectionTTLDisabled ? Long.MAX_VALUE : maxConnectionTTL, maxIdleTimeDisabled ? Long.MAX_VALUE - : maxIdleTime); - - if (!maxConnectionTTLDisabled || !maxIdleTimeDisabled) - scheduleNewIdleChannelDetector(new IdleChannelDetector()); - } - - private void scheduleNewIdleChannelDetector(TimerTask task) { - nettyTimer.newTimeout(task, cleanerPeriod, TimeUnit.MILLISECONDS); - } - - private static final class ChannelCreation { - final long creationTime; - final String poolKey; - - ChannelCreation(long creationTime, String poolKey) { - this.creationTime = creationTime; - this.poolKey = poolKey; - } - } - - private static final class IdleChannel { - final Channel channel; - final long start; - - IdleChannel(Channel channel, long start) { - if (channel == null) - throw new NullPointerException("channel"); - this.channel = channel; - this.start = start; - } - - @Override - // only depends on channel - public boolean equals(Object o) { - return this == o || (o instanceof IdleChannel && channel.equals(IdleChannel.class.cast(o).channel)); - } - - @Override - public int hashCode() { - return channel.hashCode(); - } - } - - private boolean isTTLExpired(Channel channel, long now) { - if (maxConnectionTTLDisabled) - return false; - - ChannelCreation creation = channel2Creation.get(channel); - return creation == null || now - creation.creationTime >= maxConnectionTTL; - } - - private boolean isRemotelyClosed(Channel channel) { - return !channel.isActive(); - } - - private final class IdleChannelDetector implements TimerTask { - - private boolean isIdleTimeoutExpired(IdleChannel idleChannel, long now) { - return !maxIdleTimeDisabled && now - idleChannel.start >= maxIdleTime; - } - - private List expiredChannels(ConcurrentLinkedQueue pool, long now) { - // lazy create - List idleTimeoutChannels = null; - for (IdleChannel idleChannel : pool) { - if (isTTLExpired(idleChannel.channel, now) || isIdleTimeoutExpired(idleChannel, now) - || isRemotelyClosed(idleChannel.channel)) { - LOGGER.debug("Adding Candidate expired Channel {}", idleChannel.channel); - if (idleTimeoutChannels == null) - idleTimeoutChannels = new ArrayList(); - idleTimeoutChannels.add(idleChannel); - } - } - - return idleTimeoutChannels != null ? idleTimeoutChannels : Collections. emptyList(); - } - - private boolean isChannelCloseable(Channel channel) { - Object attribute = Channels.getAttribute(channel); - if (attribute instanceof NettyResponseFuture) { - NettyResponseFuture future = (NettyResponseFuture) attribute; - if (!future.isDone()) - LOGGER.error("Future not in appropriate state %s, not closing", future); - } - return true; - } - - private final List closeChannels(List candidates) { - - // lazy create, only if we have a non-closeable channel - List closedChannels = null; - for (int i = 0; i < candidates.size(); i++) { - IdleChannel idleChannel = candidates.get(i); - if (!isChannelCloseable(idleChannel.channel)) - if (closedChannels == null) { - // first non closeable to be skipped, copy all previously skipped closeable channels - closedChannels = new ArrayList(candidates.size()); - for (int j = 0; j < i; j++) - closedChannels.add(candidates.get(j)); - } else { - LOGGER.debug("Closing Idle Channel {}", idleChannel.channel); - close(idleChannel.channel); - if (closedChannels != null) { - closedChannels.add(idleChannel); - } - } - } - - return closedChannels != null ? closedChannels : candidates; - } - - public void run(Timeout timeout) throws Exception { - - if (isClosed.get()) - return; - - try { - if (LOGGER.isDebugEnabled()) - for (String key : poolsPerKey.keySet()) { - LOGGER.debug("Entry count for : {} : {}", key, poolsPerKey.get(key).size()); - } - - long start = millisTime(); - int closedCount = 0; - int totalCount = 0; - - for (ConcurrentLinkedQueue pool : poolsPerKey.values()) { - - // store in intermediate unsynchronized lists to minimize the impact on the ConcurrentLinkedQueue - if (LOGGER.isDebugEnabled()) - totalCount += pool.size(); - - List closedChannels = closeChannels(expiredChannels(pool, start)); - - if (!closedChannels.isEmpty()) { - for (IdleChannel closedChannel : closedChannels) - channel2Creation.remove(closedChannel.channel); - - pool.removeAll(closedChannels); - closedCount += closedChannels.size(); - } - } - - long duration = millisTime() - start; - - LOGGER.debug("Closed {} connections out of {} in {}ms", closedCount, totalCount, duration); - - } catch (Throwable t) { - LOGGER.error("uncaught exception!", t); - } - - scheduleNewIdleChannelDetector(timeout.task()); - } - } - - private ConcurrentLinkedQueue getPoolForKey(String key) { - ConcurrentLinkedQueue pool = poolsPerKey.get(key); - if (pool == null) { - // lazy init pool - ConcurrentLinkedQueue newPool = new ConcurrentLinkedQueue(); - pool = poolsPerKey.putIfAbsent(key, newPool); - if (pool == null) - pool = newPool; - } - return pool; - } - - /** - * {@inheritDoc} - */ - public boolean offer(Channel channel, String poolKey) { - if (isClosed.get() || (!sslConnectionPoolEnabled && poolKey.startsWith("https"))) - return false; - - long now = millisTime(); - - if (isTTLExpired(channel, now)) - return false; - - boolean added = getPoolForKey(poolKey).add(new IdleChannel(channel, now)); - if (added) - channel2Creation.putIfAbsent(channel, new ChannelCreation(now, poolKey)); - - return added; - } - - /** - * {@inheritDoc} - */ - public Channel poll(String poolKey) { - if (!sslConnectionPoolEnabled && poolKey.startsWith("https")) - return null; - - IdleChannel idleChannel = null; - ConcurrentLinkedQueue pooledConnectionForKey = poolsPerKey.get(poolKey); - if (pooledConnectionForKey != null) { - while (idleChannel == null) { - idleChannel = pooledConnectionForKey.poll(); - - if (idleChannel == null) - // pool is empty - break; - else if (isRemotelyClosed(idleChannel.channel)) { - idleChannel = null; - LOGGER.trace("Channel not connected or not opened, probably remotely closed!"); - } - } - } - return idleChannel != null ? idleChannel.channel : null; - } - - /** - * {@inheritDoc} - */ - public boolean removeAll(Channel channel) { - ChannelCreation creation = channel2Creation.remove(channel); - return !isClosed.get() && creation != null && poolsPerKey.get(creation.poolKey).remove(channel); - } - - /** - * {@inheritDoc} - */ - public boolean isOpen() { - return !isClosed.get(); - } - - /** - * {@inheritDoc} - */ - public void destroy() { - if (isClosed.getAndSet(true)) - return; - - for (ConcurrentLinkedQueue pool : poolsPerKey.values()) { - for (IdleChannel idleChannel : pool) - close(idleChannel.channel); - } - - poolsPerKey.clear(); - channel2Creation.clear(); - } - - private void close(Channel channel) { - try { - // FIXME pity to have to do this here - Channels.setDiscard(channel); - channel2Creation.remove(channel); - channel.close(); - } catch (Throwable t) { - // noop - } - } -} \ No newline at end of file diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/NoopChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/NoopChannelPool.java deleted file mode 100755 index ad7fe1ccb3..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/NoopChannelPool.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.channel.pool; - -import io.netty.channel.Channel; - -public class NoopChannelPool implements ChannelPool { - - public boolean offer(Channel channel, String poolKey) { - return false; - } - - public Channel poll(String poolKey) { - return null; - } - - public boolean removeAll(Channel channel) { - return false; - } - - public boolean isOpen() { - return true; - } - - public void destroy() { - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java deleted file mode 100755 index 6a91149ad3..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java +++ /dev/null @@ -1,475 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.future; - -import static org.asynchttpclient.util.DateUtils.millisTime; -import io.netty.channel.Channel; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpResponse; - -import java.net.SocketAddress; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.ConnectionPoolKeyStrategy; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Request; -import org.asynchttpclient.listenable.AbstractListenableFuture; -import org.asynchttpclient.providers.netty.channel.Channels; -import org.asynchttpclient.providers.netty.request.NettyRequest; -import org.asynchttpclient.providers.netty.request.timeout.TimeoutsHolder; -import org.asynchttpclient.uri.UriComponents; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A {@link Future} that can be used to track when an asynchronous HTTP request has been fully processed. - * - * @param - */ -public final class NettyResponseFuture extends AbstractListenableFuture { - - private static final Logger LOGGER = LoggerFactory.getLogger(NettyResponseFuture.class); - - public enum STATE { - NEW, POOLED, RECONNECTED, CLOSED, - } - - private volatile boolean requestTimeoutReached; - private volatile boolean idleConnectionTimeoutReached; - private final long start = millisTime(); - private final ConnectionPoolKeyStrategy connectionPoolKeyStrategy; - private final ProxyServer proxyServer; - private final int maxRetry; - private final CountDownLatch latch = new CountDownLatch(1); - - // state mutated from outside the event loop - // TODO check if they are indeed mutated outside the event loop - private final AtomicBoolean isDone = new AtomicBoolean(false); - private final AtomicBoolean isCancelled = new AtomicBoolean(false); - private final AtomicInteger redirectCount = new AtomicInteger(); - private final AtomicBoolean inAuth = new AtomicBoolean(false); - private final AtomicBoolean statusReceived = new AtomicBoolean(false); - private final AtomicLong touch = new AtomicLong(millisTime()); - private final AtomicReference state = new AtomicReference(STATE.NEW); - private final AtomicBoolean contentProcessed = new AtomicBoolean(false); - private final AtomicInteger currentRetry = new AtomicInteger(0); - private final AtomicBoolean onThrowableCalled = new AtomicBoolean(false); - private final AtomicReference content = new AtomicReference(); - private final AtomicReference exEx = new AtomicReference(); - private volatile TimeoutsHolder timeoutsHolder; - - // state mutated only inside the event loop - private Channel channel; - private UriComponents uri; - private boolean keepAlive = true; - private Request request; - private NettyRequest nettyRequest; - private HttpHeaders httpHeaders; - private AsyncHandler asyncHandler; - private HttpResponse pendingResponse; - private boolean streamWasAlreadyConsumed; - private boolean reuseChannel; - private boolean headersAlreadyWrittenOnContinue; - private boolean dontWriteBodyBecauseExpectContinue; - private boolean allowConnect; - - public NettyResponseFuture(UriComponents uri,// - Request request,// - AsyncHandler asyncHandler,// - NettyRequest nettyRequest,// - int maxRetry,// - ConnectionPoolKeyStrategy connectionPoolKeyStrategy,// - ProxyServer proxyServer) { - - this.asyncHandler = asyncHandler; - this.request = request; - this.nettyRequest = nettyRequest; - this.uri = uri; - this.connectionPoolKeyStrategy = connectionPoolKeyStrategy; - this.proxyServer = proxyServer; - this.maxRetry = maxRetry; - } - - /*********************************************/ - /** java.util.concurrent.Future **/ - /*********************************************/ - - @Override - public boolean isDone() { - return isDone.get() || isCancelled.get(); - } - - @Override - public boolean isCancelled() { - return isCancelled.get(); - } - - @Override - public boolean cancel(boolean force) { - cancelTimeouts(); - - if (isCancelled.getAndSet(true)) - return false; - - try { - Channels.setDiscard(channel); - channel.close(); - } catch (Throwable t) { - // Ignore - } - if (!onThrowableCalled.getAndSet(true)) { - try { - asyncHandler.onThrowable(new CancellationException()); - } catch (Throwable t) { - LOGGER.warn("cancel", t); - } - } - latch.countDown(); - runListeners(); - return true; - } - - @Override - public V get() throws InterruptedException, ExecutionException { - latch.await(); - return getContent(); - } - - @Override - public V get(long l, TimeUnit tu) throws InterruptedException, TimeoutException, ExecutionException { - if (!latch.await(l, tu)) - throw new TimeoutException(); - return getContent(); - } - - private V getContent() throws ExecutionException { - - ExecutionException e = exEx.get(); - if (e != null) - throw e; - - V update = content.get(); - // No more retry - currentRetry.set(maxRetry); - if (!contentProcessed.getAndSet(true)) { - try { - update = asyncHandler.onCompleted(); - } catch (Throwable ex) { - if (!onThrowableCalled.getAndSet(true)) { - try { - try { - asyncHandler.onThrowable(ex); - } catch (Throwable t) { - LOGGER.debug("asyncHandler.onThrowable", t); - } - throw new RuntimeException(ex); - } finally { - cancelTimeouts(); - } - } - } - content.compareAndSet(null, update); - } - return update; - } - - /*********************************************/ - /** org.asynchttpclient.ListenableFuture **/ - /*********************************************/ - - public final void done() { - - cancelTimeouts(); - - if (isDone.getAndSet(true) || isCancelled.get()) - return; - - try { - getContent(); - - } catch (ExecutionException t) { - return; - } catch (RuntimeException t) { - Throwable exception = t.getCause() != null ? t.getCause() : t; - exEx.compareAndSet(null, new ExecutionException(exception)); - - } finally { - latch.countDown(); - } - - runListeners(); - } - - public final void abort(final Throwable t) { - - cancelTimeouts(); - - if (isDone.get() || isCancelled.getAndSet(true)) - return; - - exEx.compareAndSet(null, new ExecutionException(t)); - if (onThrowableCalled.compareAndSet(false, true)) { - try { - asyncHandler.onThrowable(t); - } catch (Throwable te) { - LOGGER.debug("asyncHandler.onThrowable", te); - } - } - latch.countDown(); - runListeners(); - } - - @Override - public void touch() { - touch.set(millisTime()); - } - - /*********************************************/ - /** INTERNAL **/ - /*********************************************/ - - public UriComponents getURI() { - return uri; - } - - public void setURI(UriComponents uri) { - this.uri = uri; - } - - public ConnectionPoolKeyStrategy getConnectionPoolKeyStrategy() { - return connectionPoolKeyStrategy; - } - - public ProxyServer getProxyServer() { - return proxyServer; - } - - public void setAsyncHandler(AsyncHandler asyncHandler) { - this.asyncHandler = asyncHandler; - } - - /** - * Is the Future still valid - * - * @return true if response has expired and should be terminated. - */ - public boolean hasExpired() { - return requestTimeoutReached || idleConnectionTimeoutReached; - } - - public void setRequestTimeoutReached() { - this.requestTimeoutReached = true; - } - - public boolean isRequestTimeoutReached() { - return requestTimeoutReached; - } - - public void setIdleConnectionTimeoutReached() { - this.idleConnectionTimeoutReached = true; - } - - public boolean isIdleConnectionTimeoutReached() { - return idleConnectionTimeoutReached; - } - - public void cancelTimeouts() { - if (timeoutsHolder != null) { - timeoutsHolder.cancel(); - timeoutsHolder = null; - } - } - - public final Request getRequest() { - return request; - } - - public final NettyRequest getNettyRequest() { - return nettyRequest; - } - - public final void setNettyRequest(NettyRequest nettyRequest) { - this.nettyRequest = nettyRequest; - } - - public final AsyncHandler getAsyncHandler() { - return asyncHandler; - } - - public final boolean isKeepAlive() { - return keepAlive; - } - - public final void setKeepAlive(final boolean keepAlive) { - this.keepAlive = keepAlive; - } - - public final HttpHeaders getHttpHeaders() { - return httpHeaders; - } - - public final void setHttpHeaders(HttpHeaders httpHeaders) { - this.httpHeaders = httpHeaders; - } - - public int incrementAndGetCurrentRedirectCount() { - return redirectCount.incrementAndGet(); - } - - public void setTimeoutsHolder(TimeoutsHolder timeoutsHolder) { - this.timeoutsHolder = timeoutsHolder; - } - - public boolean isInAuth() { - return inAuth.get(); - } - - public boolean getAndSetAuth(boolean inDigestAuth) { - return inAuth.getAndSet(inDigestAuth); - } - - public STATE getState() { - return state.get(); - } - - public void setState(STATE state) { - this.state.set(state); - } - - public boolean getAndSetStatusReceived(boolean sr) { - return statusReceived.getAndSet(sr); - } - - public HttpResponse getPendingResponse() { - return pendingResponse; - } - - public void setPendingResponse(HttpResponse pendingResponse) { - this.pendingResponse = pendingResponse; - } - - public boolean isStreamWasAlreadyConsumed() { - return streamWasAlreadyConsumed; - } - - public void setStreamWasAlreadyConsumed(boolean streamWasAlreadyConsumed) { - this.streamWasAlreadyConsumed = streamWasAlreadyConsumed; - } - - public long getLastTouch() { - return touch.get(); - } - - public void setHeadersAlreadyWrittenOnContinue(boolean headersAlreadyWrittenOnContinue) { - this.headersAlreadyWrittenOnContinue = headersAlreadyWrittenOnContinue; - } - - public boolean isHeadersAlreadyWrittenOnContinue() { - return headersAlreadyWrittenOnContinue; - } - - public void setDontWriteBodyBecauseExpectContinue(boolean dontWriteBodyBecauseExpectContinue) { - this.dontWriteBodyBecauseExpectContinue = dontWriteBodyBecauseExpectContinue; - } - - public boolean isDontWriteBodyBecauseExpectContinue() { - return dontWriteBodyBecauseExpectContinue; - } - - public void setReuseChannel(boolean reuseChannel) { - this.reuseChannel = reuseChannel; - } - - public boolean isConnectAllowed() { - return allowConnect; - } - - public void setConnectAllowed(boolean allowConnect) { - this.allowConnect = allowConnect; - } - - public void attachChannel(Channel channel, boolean reuseChannel) { - this.channel = channel; - this.reuseChannel = reuseChannel; - } - - public Channel channel() { - return channel; - } - - public boolean reuseChannel() { - return reuseChannel; - } - - public boolean canRetry() { - if (currentRetry.incrementAndGet() > maxRetry) { - return false; - } - return true; - } - - public SocketAddress getChannelRemoteAddress() { - return channel() != null ? channel().remoteAddress() : null; - } - - public void setRequest(Request request) { - this.request = request; - } - - /** - * Return true if the {@link Future} can be recovered. There is some scenario where a connection can be closed by an - * unexpected IOException, and in some situation we can recover from that exception. - * - * @return true if that {@link Future} cannot be recovered. - */ - public boolean canBeReplayed() { - return !isDone() && canRetry() - && !(Channels.isChannelValid(channel) && !uri.getScheme().equalsIgnoreCase("https")) && !isInAuth(); - } - - public long getStart() { - return start; - } - - @Override - public String toString() { - return "NettyResponseFuture{" + // - "currentRetry=" + currentRetry + // - ",\n\tisDone=" + isDone + // - ",\n\tisCancelled=" + isCancelled + // - ",\n\tasyncHandler=" + asyncHandler + // - ",\n\tnettyRequest=" + nettyRequest + // - ",\n\tcontent=" + content + // - ",\n\turi=" + uri + // - ",\n\tkeepAlive=" + keepAlive + // - ",\n\thttpHeaders=" + httpHeaders + // - ",\n\texEx=" + exEx + // - ",\n\tredirectCount=" + redirectCount + // - ",\n\ttimeoutsHolder=" + timeoutsHolder + // - ",\n\tinAuth=" + inAuth + // - ",\n\tstatusReceived=" + statusReceived + // - ",\n\ttouch=" + touch + // - '}'; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java deleted file mode 100755 index f4a6589768..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.future; - -public class StackTraceInspector { - - private static boolean exceptionInMethod(Throwable t, String className, String methodName) { - try { - for (StackTraceElement element : t.getStackTrace()) { - if (element.getClassName().equals(className) && element.getMethodName().equals(methodName)) - return true; - } - } catch (Throwable ignore) { - } - return false; - } - - private static boolean abortOnConnectCloseException(Throwable t) { - return exceptionInMethod(t, "sun.nio.ch.SocketChannelImpl", "checkConnect") - || (t.getCause() != null && abortOnConnectCloseException(t.getCause())); - } - - public static boolean abortOnDisconnectException(Throwable t) { - return exceptionInMethod(t, "io.netty.handler.ssl.SslHandler", "disconnect") - || (t.getCause() != null && abortOnConnectCloseException(t.getCause())); - } - - public static boolean abortOnReadOrWriteException(Throwable t) { - - try { - for (StackTraceElement element : t.getStackTrace()) { - String className = element.getClassName(); - String methodName = element.getMethodName(); - if (className.equals("sun.nio.ch.SocketDispatcher") && (methodName.equals("read") || methodName.equals("write"))) - return true; - } - } catch (Throwable ignore) { - } - - if (t.getCause() != null) - return abortOnReadOrWriteException(t.getCause()); - - return false; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java deleted file mode 100755 index d9760cae84..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java +++ /dev/null @@ -1,474 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.handler; - -import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE; -import static io.netty.handler.codec.http.HttpResponseStatus.OK; -import static io.netty.handler.codec.http.HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED; -import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED; -import static org.asynchttpclient.providers.netty.util.HttpUtils.isNTLM; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getDefaultPort; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -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; - -import java.io.IOException; -import java.util.List; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHandler.STATE; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.ntlm.NTLMEngine; -import org.asynchttpclient.ntlm.NTLMEngineException; -import org.asynchttpclient.providers.netty.Callback; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.providers.netty.channel.ChannelManager; -import org.asynchttpclient.providers.netty.channel.Channels; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.request.NettyRequestSender; -import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; -import org.asynchttpclient.providers.netty.response.NettyResponseHeaders; -import org.asynchttpclient.providers.netty.response.NettyResponseStatus; -import org.asynchttpclient.spnego.SpnegoEngine; -import org.asynchttpclient.uri.UriComponents; - -public final class HttpProtocol extends Protocol { - - public HttpProtocol(ChannelManager channelManager, AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, NettyRequestSender requestSender) { - super(channelManager, config, nettyConfig, requestSender); - } - - private Realm.RealmBuilder newRealmBuilder(Realm realm) { - return realm != null ? new Realm.RealmBuilder().clone(realm) : new Realm.RealmBuilder(); - } - - private Realm kerberosChallenge(Channel channel, List proxyAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, - NettyResponseFuture future, boolean proxyInd) throws NTLMEngineException { - - UriComponents uri = request.getURI(); - String host = request.getVirtualHost() == null ? uri.getHost() : request.getVirtualHost(); - String server = proxyServer == null ? host : proxyServer.getHost(); - try { - String challengeHeader = SpnegoEngine.instance().generateToken(server); - headers.remove(HttpHeaders.Names.AUTHORIZATION); - headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); - - return newRealmBuilder(realm)// - .setUri(uri)// - .setMethodName(request.getMethod())// - .setScheme(Realm.AuthScheme.KERBEROS)// - .build(); - - } catch (Throwable throwable) { - if (isNTLM(proxyAuth)) { - return ntlmChallenge(proxyAuth, request, proxyServer, headers, realm, future, proxyInd); - } - requestSender.abort(channel, future, throwable); - return null; - } - } - - private String authorizationHeaderName(boolean proxyInd) { - return proxyInd ? HttpHeaders.Names.PROXY_AUTHORIZATION : HttpHeaders.Names.AUTHORIZATION; - } - - private void addNTLMAuthorizationHeader(FluentCaseInsensitiveStringsMap headers, String challengeHeader, boolean proxyInd) { - headers.add(authorizationHeaderName(proxyInd), "NTLM " + challengeHeader); - } - - private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, - NettyResponseFuture future, boolean proxyInd) throws NTLMEngineException { - - boolean useRealm = proxyServer == null && realm != null; - - String ntlmDomain = useRealm ? realm.getNtlmDomain() : proxyServer.getNtlmDomain(); - String ntlmHost = useRealm ? realm.getNtlmHost() : proxyServer.getHost(); - String principal = useRealm ? realm.getPrincipal() : proxyServer.getPrincipal(); - String password = useRealm ? realm.getPassword() : proxyServer.getPassword(); - UriComponents uri = request.getURI(); - - if (realm != null && !realm.isNtlmMessageType2Received()) { - String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg(ntlmDomain, ntlmHost); - - addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd); - future.getAndSetAuth(false); - return newRealmBuilder(realm)// - .setScheme(realm.getAuthScheme())// - .setUri(uri)// - .setMethodName(request.getMethod())// - .setNtlmMessageType2Received(true)// - .build(); - - } else { - addType3NTLMAuthorizationHeader(wwwAuth, headers, principal, password, ntlmDomain, ntlmHost, proxyInd); - Realm.AuthScheme authScheme = realm != null ? realm.getAuthScheme() : Realm.AuthScheme.NTLM; - return newRealmBuilder(realm)// - .setScheme(authScheme)// - .setUri(uri)// - .setMethodName(request.getMethod())// - .build(); - } - } - - private Realm ntlmProxyChallenge(List wwwAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, - NettyResponseFuture future, boolean proxyInd) throws NTLMEngineException { - future.getAndSetAuth(false); - headers.remove(HttpHeaders.Names.PROXY_AUTHORIZATION); - - addType3NTLMAuthorizationHeader(wwwAuth, headers, proxyServer.getPrincipal(), proxyServer.getPassword(), proxyServer.getNtlmDomain(), proxyServer.getHost(), proxyInd); - - return newRealmBuilder(realm)// - // .setScheme(realm.getAuthScheme()) - .setUri(request.getURI())// - .setMethodName(request.getMethod()).build(); - } - - private void addType3NTLMAuthorizationHeader(List auth, FluentCaseInsensitiveStringsMap headers, String username, String password, String domain, String workstation, - boolean proxyInd) throws NTLMEngineException { - headers.remove(authorizationHeaderName(proxyInd)); - - if (isNonEmpty(auth) && auth.get(0).startsWith("NTLM ")) { - String serverChallenge = auth.get(0).trim().substring("NTLM ".length()); - String challengeHeader = NTLMEngine.INSTANCE.generateType3Msg(username, password, domain, workstation, serverChallenge); - addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd); - } - } - - private void finishUpdate(final NettyResponseFuture future, Channel channel, boolean expectOtherChunks) throws IOException { - - boolean keepAlive = future.isKeepAlive(); - if (expectOtherChunks && keepAlive) - channelManager.drainChannel(channel, future); - else - channelManager.tryToOfferChannelToPool(channel, keepAlive, channelManager.getPoolKey(future)); - markAsDone(future, channel); - } - - private boolean updateBodyAndInterrupt(NettyResponseFuture future, AsyncHandler handler, NettyResponseBodyPart bodyPart) throws Exception { - boolean interrupt = handler.onBodyPartReceived(bodyPart) != STATE.CONTINUE; - if (bodyPart.isUnderlyingConnectionToBeClosed()) - future.setKeepAlive(false); - return interrupt; - } - - private void markAsDone(NettyResponseFuture future, final Channel channel) { - // We need to make sure everything is OK before adding the - // connection back to the pool. - try { - future.done(); - } catch (Throwable t) { - // Never propagate exception once we know we are done. - logger.debug(t.getMessage(), t); - } - - if (!future.isKeepAlive() || !channel.isActive()) { - channelManager.closeChannel(channel); - } - } - - private boolean exitAfterHandling401(// - final Channel channel,// - final NettyResponseFuture future,// - HttpResponse response,// - final Request request,// - int statusCode,// - Realm realm,// - ProxyServer proxyServer) throws Exception { - - if (statusCode == UNAUTHORIZED.code() && realm != null && !future.getAndSetAuth(true)) { - - List wwwAuthHeaders = response.headers().getAll(HttpHeaders.Names.WWW_AUTHENTICATE); - - if (!wwwAuthHeaders.isEmpty()) { - future.setState(NettyResponseFuture.STATE.NEW); - Realm newRealm = null; - boolean negociate = wwwAuthHeaders.contains("Negotiate"); - if (!wwwAuthHeaders.contains("Kerberos") && (isNTLM(wwwAuthHeaders) || negociate)) { - // NTLM - newRealm = ntlmChallenge(wwwAuthHeaders, request, proxyServer, request.getHeaders(), realm, future, false); - } else if (negociate) { - newRealm = kerberosChallenge(channel, wwwAuthHeaders, request, proxyServer, request.getHeaders(), realm, future, false); - // SPNEGO KERBEROS - if (newRealm == null) - return true; - } else { - newRealm = new Realm.RealmBuilder()// - .clone(realm)// - .setScheme(realm.getAuthScheme())// - .setUri(request.getURI())// - .setMethodName(request.getMethod())// - .setUsePreemptiveAuth(true)// - .parseWWWAuthenticateHeader(wwwAuthHeaders.get(0))// - .build(); - } - - Realm nr = newRealm; - final Request nextRequest = new RequestBuilder(future.getRequest()).setHeaders(request.getHeaders()).setRealm(nr).build(); - - logger.debug("Sending authentication to {}", request.getURI()); - Callback callback = new Callback(future) { - public void call() throws Exception { - channelManager.drainChannel(channel, future); - requestSender.sendNextRequest(nextRequest, future); - } - }; - - if (future.isKeepAlive() && HttpHeaders.isTransferEncodingChunked(response)) - // We must make sure there is no bytes left - // before executing the next request. - Channels.setAttribute(channel, callback); - else - callback.call(); - - return true; - } - } - - return false; - } - - private boolean exitAfterHandling100(final Channel channel, final NettyResponseFuture future, int statusCode) { - if (statusCode == CONTINUE.code()) { - future.setHeadersAlreadyWrittenOnContinue(true); - future.setDontWriteBodyBecauseExpectContinue(false); - // FIXME why not reuse the channel? - requestSender.writeRequest(future, channel); - return true; - - } - return false; - } - - private boolean exitAfterHandling407(// - Channel channel,// - NettyResponseFuture future,// - HttpResponse response,// - Request request,// - int statusCode,// - Realm realm,// - ProxyServer proxyServer) throws Exception { - - if (statusCode == PROXY_AUTHENTICATION_REQUIRED.code() && realm != null && !future.getAndSetAuth(true)) { - - List proxyAuthHeaders = response.headers().getAll(HttpHeaders.Names.PROXY_AUTHENTICATE); - - if (!proxyAuthHeaders.isEmpty()) { - logger.debug("Sending proxy authentication to {}", request.getURI()); - - future.setState(NettyResponseFuture.STATE.NEW); - Realm newRealm = null; - FluentCaseInsensitiveStringsMap requestHeaders = request.getHeaders(); - - boolean negociate = proxyAuthHeaders.contains("Negotiate"); - if (!proxyAuthHeaders.contains("Kerberos") && (isNTLM(proxyAuthHeaders) || negociate)) { - newRealm = ntlmProxyChallenge(proxyAuthHeaders, request, proxyServer, requestHeaders, realm, future, true); - // SPNEGO KERBEROS - } else if (negociate) { - newRealm = kerberosChallenge(channel, proxyAuthHeaders, request, proxyServer, requestHeaders, realm, future, true); - if (newRealm == null) - return true; - } else { - newRealm = new Realm.RealmBuilder().clone(realm)// - .setScheme(realm.getAuthScheme())// - .setUri(request.getURI())// - .setOmitQuery(true)// - .setMethodName(HttpMethod.CONNECT.name())// - .setUsePreemptiveAuth(true)// - .parseProxyAuthenticateHeader(proxyAuthHeaders.get(0))// - .build(); - } - - future.setReuseChannel(true); - future.setConnectAllowed(true); - Request nextRequest = new RequestBuilder(future.getRequest()).setHeaders(requestHeaders).setRealm(newRealm).build(); - requestSender.sendNextRequest(nextRequest, future); - return true; - } - } - return false; - } - - private boolean exitAfterHandlingConnect(// - final Channel channel,// - final NettyResponseFuture future,// - final Request request,// - ProxyServer proxyServer,// - int statusCode,// - HttpRequest httpRequest) throws IOException { - - if (statusCode == OK.code() && httpRequest.getMethod() == HttpMethod.CONNECT) { - - if (future.isKeepAlive()) - future.attachChannel(channel, true); - - try { - UriComponents requestURI = request.getURI(); - String scheme = requestURI.getScheme(); - String host = requestURI.getHost(); - int port = getDefaultPort(requestURI); - - logger.debug("Connecting to proxy {} for scheme {}", proxyServer, scheme); - channelManager.upgradeProtocol(channel.pipeline(), scheme, host, port); - - } catch (Throwable ex) { - requestSender.abort(channel, future, ex); - } - - future.setReuseChannel(true); - future.setConnectAllowed(false); - requestSender.sendNextRequest(new RequestBuilder(future.getRequest()).build(), future); - return true; - } - - return false; - } - - private boolean exitAfterHandlingStatus(Channel channel, NettyResponseFuture future, HttpResponse response, AsyncHandler handler, NettyResponseStatus status) - throws IOException, Exception { - if (!future.getAndSetStatusReceived(true) && handler.onStatusReceived(status) != STATE.CONTINUE) { - finishUpdate(future, channel, HttpHeaders.isTransferEncodingChunked(response)); - return true; - } - return false; - } - - private boolean exitAfterHandlingHeaders(Channel channel, NettyResponseFuture future, HttpResponse response, AsyncHandler handler, NettyResponseHeaders responseHeaders) - throws IOException, Exception { - if (!response.headers().isEmpty() && handler.onHeadersReceived(responseHeaders) != STATE.CONTINUE) { - finishUpdate(future, channel, HttpHeaders.isTransferEncodingChunked(response)); - return true; - } - return false; - } - - private boolean handleHttpResponse(final HttpResponse response, final Channel channel, final NettyResponseFuture future, AsyncHandler handler) throws Exception { - - HttpRequest httpRequest = future.getNettyRequest().getHttpRequest(); - ProxyServer proxyServer = future.getProxyServer(); - logger.debug("\n\nRequest {}\n\nResponse {}\n", httpRequest, response); - - // store the original headers so we can re-send all them to - // the handler in case of trailing headers - future.setHttpHeaders(response.headers()); - - future.setKeepAlive(!HttpHeaders.Values.CLOSE.equalsIgnoreCase(response.headers().get(HttpHeaders.Names.CONNECTION))); - - NettyResponseStatus status = new NettyResponseStatus(future.getURI(), config, response); - int statusCode = response.getStatus().code(); - Request request = future.getRequest(); - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - NettyResponseHeaders responseHeaders = new NettyResponseHeaders(response.headers()); - - return exitAfterProcessingFilters(channel, future, handler, status, responseHeaders) - || exitAfterHandling401(channel, future, response, request, statusCode, realm, proxyServer) || // - exitAfterHandling407(channel, future, response, request, statusCode, realm, proxyServer) || // - exitAfterHandling100(channel, future, statusCode) || // - exitAfterHandlingRedirect(channel, future, response, request, statusCode) || // - exitAfterHandlingConnect(channel, future, request, proxyServer, statusCode, httpRequest) || // - exitAfterHandlingStatus(channel, future, response, handler, status) || // - exitAfterHandlingHeaders(channel, future, response, handler, responseHeaders); - } - - @Override - public void handle(final Channel channel, final NettyResponseFuture future, final Object e) throws Exception { - - future.touch(); - - // future is already done because of an exception or a timeout - if (future.isDone()) { - // FIXME isn't the channel already properly closed? - channelManager.closeChannel(channel); - return; - } - - AsyncHandler handler = future.getAsyncHandler(); - try { - if (e instanceof HttpResponse) { - HttpResponse response = (HttpResponse) e; - // we buffer the response until we get the LastHttpContent - future.setPendingResponse(response); - return; - - } else if (e instanceof HttpContent) { - HttpResponse response = future.getPendingResponse(); - future.setPendingResponse(null); - if (response != null && handleHttpResponse(response, channel, future, handler)) - return; - - HttpContent chunk = (HttpContent) e; - - boolean interrupt = false; - boolean last = chunk instanceof LastHttpContent; - - // Netty 4: the last chunk is not empty - if (last) { - LastHttpContent lastChunk = (LastHttpContent) chunk; - HttpHeaders trailingHeaders = lastChunk.trailingHeaders(); - if (!trailingHeaders.isEmpty()) { - NettyResponseHeaders responseHeaders = new NettyResponseHeaders(future.getHttpHeaders(), trailingHeaders); - interrupt = handler.onHeadersReceived(responseHeaders) != STATE.CONTINUE; - } - } - - ByteBuf buf = chunk.content(); - try { - if (!interrupt && (buf.readableBytes() > 0 || last)) { - NettyResponseBodyPart part = nettyConfig.getBodyPartFactory().newResponseBodyPart(buf, last); - interrupt = updateBodyAndInterrupt(future, handler, part); - } - } finally { - // FIXME we shouldn't need this, should we? But a leak was reported there without it?! - buf.release(); - } - - if (interrupt || last) - finishUpdate(future, channel, !last); - } - } catch (Exception t) { - if (hasIOExceptionFilters// - && t instanceof IOException// - && requestSender.applyIoExceptionFiltersAndReplayRequest(future, IOException.class.cast(t), channel)) { - return; - } - - try { - requestSender.abort(channel, future, t); - } catch (Exception abortException) { - logger.debug("Abort failed", abortException); - } finally { - finishUpdate(future, channel, false); - } - throw t; - } - } - - @Override - public void onError(NettyResponseFuture future, Throwable error) { - } - - @Override - public void onClose(NettyResponseFuture future) { - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java deleted file mode 100755 index 0e624283b9..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.handler; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.REMOTELY_CLOSED_EXCEPTION; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandler.Sharable; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.handler.codec.PrematureChannelClosureException; -import io.netty.handler.codec.http.LastHttpContent; - -import java.io.IOException; -import java.nio.channels.ClosedChannelException; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.Callback; -import org.asynchttpclient.providers.netty.DiscardEvent; -import org.asynchttpclient.providers.netty.channel.ChannelManager; -import org.asynchttpclient.providers.netty.channel.Channels; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.future.StackTraceInspector; -import org.asynchttpclient.providers.netty.request.NettyRequestSender; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Sharable -public class Processor extends ChannelInboundHandlerAdapter { - - private static final Logger LOGGER = LoggerFactory.getLogger(Processor.class); - - public static final IOException CHANNEL_CLOSED_EXCEPTION = new IOException("Channel Closed"); - static { - CHANNEL_CLOSED_EXCEPTION.setStackTrace(new StackTraceElement[0]); - } - - private final AsyncHttpClientConfig config; - private final ChannelManager channelManager; - private final NettyRequestSender requestSender; - private final Protocol protocol; - - public Processor(AsyncHttpClientConfig config,// - ChannelManager channelManager,// - NettyRequestSender requestSender,// - Protocol protocol) { - this.config = config; - this.channelManager = channelManager; - this.requestSender = requestSender; - this.protocol = protocol; - } - - @Override - public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { - - Channel channel = ctx.channel(); - Object attribute = Channels.getAttribute(channel); - - if (attribute instanceof Callback && msg instanceof LastHttpContent) { - Callback ac = (Callback) attribute; - ac.call(); - Channels.setAttribute(channel, DiscardEvent.INSTANCE); - - } else if (attribute instanceof NettyResponseFuture) { - NettyResponseFuture future = (NettyResponseFuture) attribute; - protocol.handle(channel, future, msg); - - } else if (attribute != DiscardEvent.INSTANCE) { - try { - LOGGER.trace("Closing an orphan channel {}", channel); - channel.close(); - } catch (Throwable t) { - } - } - } - - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - - if (requestSender.isClosed()) - return; - - Channel channel = ctx.channel(); - channelManager.removeAll(channel); - - try { - super.channelInactive(ctx); - } catch (Exception ex) { - LOGGER.trace("super.channelClosed", ex); - } - - Object attribute = Channels.getAttribute(channel); - LOGGER.debug("Channel Closed: {} with attribute {}", channel, attribute); - - if (attribute instanceof Callback) { - Callback callback = (Callback) attribute; - Channels.setAttribute(channel, callback.future()); - callback.call(); - - } else if (attribute instanceof NettyResponseFuture) { - NettyResponseFuture future = NettyResponseFuture.class.cast(attribute); - future.touch(); - - if (!config.getIOExceptionFilters().isEmpty() && requestSender.applyIoExceptionFiltersAndReplayRequest(future, CHANNEL_CLOSED_EXCEPTION, channel)) - return; - - protocol.onClose(future); - - if (future.isDone()) - channelManager.closeChannel(channel); - - else if (!requestSender.retry(future)) - requestSender.abort(channel, future, REMOTELY_CLOSED_EXCEPTION); - } - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception { - Throwable cause = e.getCause() != null ? e.getCause() : e; - - if (cause instanceof PrematureChannelClosureException || cause instanceof ClosedChannelException) - return; - - Channel channel = ctx.channel(); - NettyResponseFuture future = null; - - LOGGER.debug("Unexpected I/O exception on channel {}", channel, cause); - - try { - Object attribute = Channels.getAttribute(channel); - if (attribute instanceof NettyResponseFuture) { - future = (NettyResponseFuture) attribute; - future.attachChannel(null, false); - future.touch(); - - if (cause instanceof IOException) { - - // FIXME why drop the original exception and throw a new one? - if (!config.getIOExceptionFilters().isEmpty()) { - if (!requestSender.applyIoExceptionFiltersAndReplayRequest(future, CHANNEL_CLOSED_EXCEPTION, channel)) { - // Close the channel so the recovering can occurs. - try { - channel.close(); - } catch (Throwable t) { - // Swallow. - } - } - return; - } - } - - if (StackTraceInspector.abortOnReadOrWriteException(cause)) { - LOGGER.debug("Trying to recover from dead Channel: {}", channel); - return; - } - } else if (attribute instanceof Callback) { - future = Callback.class.cast(attribute).future(); - } - } catch (Throwable t) { - cause = t; - } - - if (future != null) - try { - LOGGER.debug("Was unable to recover Future: {}", future); - requestSender.abort(channel, future, cause); - protocol.onError(future, e); - } catch (Throwable t) { - LOGGER.error(t.getMessage(), t); - } - - channelManager.closeChannel(channel); - // FIXME not really sure - // ctx.fireChannelRead(e); - ctx.close(); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Protocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Protocol.java deleted file mode 100755 index 458ca36587..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Protocol.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.handler; - -import static io.netty.handler.codec.http.HttpResponseStatus.FOUND; -import static io.netty.handler.codec.http.HttpResponseStatus.MOVED_PERMANENTLY; -import static io.netty.handler.codec.http.HttpResponseStatus.SEE_OTHER; -import static io.netty.handler.codec.http.HttpResponseStatus.TEMPORARY_REDIRECT; -import static org.asynchttpclient.providers.netty.util.HttpUtils.HTTP; -import static org.asynchttpclient.providers.netty.util.HttpUtils.WEBSOCKET; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.followRedirect; -import io.netty.channel.Channel; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpResponse; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.MaxRedirectException; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.cookie.CookieDecoder; -import org.asynchttpclient.date.TimeConverter; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.providers.netty.Callback; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.providers.netty.channel.ChannelManager; -import org.asynchttpclient.providers.netty.channel.Channels; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.request.NettyRequestSender; -import org.asynchttpclient.uri.UriComponents; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public abstract class Protocol { - - protected final Logger logger = LoggerFactory.getLogger(getClass()); - - protected final ChannelManager channelManager; - protected final AsyncHttpClientConfig config; - protected final NettyAsyncHttpProviderConfig nettyConfig; - protected final NettyRequestSender requestSender; - - private final boolean hasResponseFilters; - protected final boolean hasIOExceptionFilters; - private final TimeConverter timeConverter; - - public static final Set REDIRECT_STATUSES = new HashSet(); - static { - REDIRECT_STATUSES.add(MOVED_PERMANENTLY.code()); - REDIRECT_STATUSES.add(FOUND.code()); - REDIRECT_STATUSES.add(SEE_OTHER.code()); - REDIRECT_STATUSES.add(TEMPORARY_REDIRECT.code()); - } - - public Protocol(ChannelManager channelManager, AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, - NettyRequestSender requestSender) { - this.channelManager = channelManager; - this.config = config; - this.requestSender = requestSender; - this.nettyConfig = nettyConfig; - - hasResponseFilters = !config.getResponseFilters().isEmpty(); - hasIOExceptionFilters = !config.getIOExceptionFilters().isEmpty(); - timeConverter = config.getTimeConverter(); - } - - public abstract void handle(Channel channel, NettyResponseFuture future, Object message) throws Exception; - - public abstract void onError(NettyResponseFuture future, Throwable error); - - public abstract void onClose(NettyResponseFuture future); - - protected boolean exitAfterHandlingRedirect(// - Channel channel,// - NettyResponseFuture future,// - HttpResponse response,// - Request request,// - int statusCode) throws Exception { - - if (followRedirect(config, request) && REDIRECT_STATUSES.contains(statusCode)) { - if (future.incrementAndGetCurrentRedirectCount() >= config.getMaxRedirects()) { - throw new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()); - - } else { - // We must allow 401 handling again. - future.getAndSetAuth(false); - - HttpHeaders responseHeaders = response.headers(); - String location = responseHeaders.get(HttpHeaders.Names.LOCATION); - UriComponents uri = UriComponents.create(future.getURI(), location); - - if (!uri.equals(future.getURI())) { - final RequestBuilder requestBuilder = new RequestBuilder(future.getRequest()); - - if (!config.isRemoveQueryParamOnRedirect()) - requestBuilder.addQueryParams(future.getRequest().getQueryParams()); - - // if we are to strictly handle 302, we should keep the original method (which browsers don't) - // 303 must force GET - if ((statusCode == FOUND.code() && !config.isStrict302Handling()) || statusCode == SEE_OTHER.code()) - requestBuilder.setMethod("GET"); - - // in case of a redirect from HTTP to HTTPS, future attributes might change - final boolean initialConnectionKeepAlive = future.isKeepAlive(); - final String initialPoolKey = channelManager.getPoolKey(future); - - future.setURI(uri); - String newUrl = uri.toString(); - if (request.getURI().getScheme().startsWith(WEBSOCKET)) { - newUrl = newUrl.replaceFirst(HTTP, WEBSOCKET); - } - - logger.debug("Redirecting to {}", newUrl); - - for (String cookieStr : responseHeaders.getAll(HttpHeaders.Names.SET_COOKIE)) { - Cookie c = CookieDecoder.decode(cookieStr, timeConverter); - if (c != null) - requestBuilder.addOrReplaceCookie(c); - } - - Callback callback = channelManager.newDrainCallback(future, channel, initialConnectionKeepAlive, initialPoolKey); - - if (HttpHeaders.isTransferEncodingChunked(response)) { - // We must make sure there is no bytes left before - // executing the next request. - // FIXME investigate this - Channels.setAttribute(channel, callback); - } else { - // FIXME don't understand: this offers the connection to the pool, or even closes it, while the - // request has not been sent, right? - callback.call(); - } - - Request redirectRequest = requestBuilder.setUrl(newUrl).build(); - // FIXME why not reuse the channel is same host? - requestSender.sendNextRequest(redirectRequest, future); - return true; - } - } - } - return false; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - protected boolean exitAfterProcessingFilters(// - Channel channel,// - NettyResponseFuture future,// - AsyncHandler handler, // - HttpResponseStatus status,// - HttpResponseHeaders responseHeaders) throws IOException { - - if (hasResponseFilters) { - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(handler).request(future.getRequest()) - .responseStatus(status).responseHeaders(responseHeaders).build(); - - for (ResponseFilter asyncFilter : config.getResponseFilters()) { - try { - fc = asyncFilter.filter(fc); - // FIXME Is it worth protecting against this? - if (fc == null) { - throw new NullPointerException("FilterContext is null"); - } - } catch (FilterException efe) { - requestSender.abort(channel, future, efe); - } - } - - // The handler may have been wrapped. - future.setAsyncHandler(fc.getAsyncHandler()); - - // The request has changed - if (fc.replayRequest()) { - requestSender.replayRequest(future, fc, channel); - return true; - } - } - return false; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java deleted file mode 100755 index 5667e58396..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.handler; - -import static io.netty.handler.codec.http.HttpResponseStatus.SWITCHING_PROTOCOLS; -import static org.asynchttpclient.providers.netty.ws.WebSocketUtils.getAcceptKey; -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.LastHttpContent; -import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; -import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; -import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; -import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; -import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; -import io.netty.handler.codec.http.websocketx.WebSocketFrame; - -import java.io.IOException; -import java.util.Locale; - -import org.asynchttpclient.AsyncHandler.STATE; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Request; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.providers.netty.channel.ChannelManager; -import org.asynchttpclient.providers.netty.channel.Channels; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.request.NettyRequestSender; -import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; -import org.asynchttpclient.providers.netty.response.NettyResponseHeaders; -import org.asynchttpclient.providers.netty.response.NettyResponseStatus; -import org.asynchttpclient.providers.netty.ws.NettyWebSocket; -import org.asynchttpclient.websocket.WebSocketUpgradeHandler; - -public final class WebSocketProtocol extends Protocol { - - public WebSocketProtocol(ChannelManager channelManager,// - AsyncHttpClientConfig config,// - NettyAsyncHttpProviderConfig nettyConfig,// - NettyRequestSender requestSender) { - super(channelManager, config, nettyConfig, requestSender); - } - - // We don't need to synchronize as replacing the "ws-decoder" will - // process using the same thread. - private void invokeOnSucces(Channel channel, WebSocketUpgradeHandler h) { - if (!h.touchSuccess()) { - try { - h.onSuccess(nettyConfig.getNettyWebSocketFactory().newNettyWebSocket(channel, nettyConfig)); - } catch (Exception ex) { - logger.warn("onSuccess unexpected exception", ex); - } - } - } - - @Override - public void handle(Channel channel, NettyResponseFuture future, Object e) throws Exception { - WebSocketUpgradeHandler handler = WebSocketUpgradeHandler.class.cast(future.getAsyncHandler()); - Request request = future.getRequest(); - - if (e instanceof HttpResponse) { - HttpResponse response = (HttpResponse) e; - // we buffer the response until we get the LastHttpContent - future.setPendingResponse(response); - - } else if (e instanceof LastHttpContent) { - HttpResponse response = future.getPendingResponse(); - future.setPendingResponse(null); - HttpResponseStatus status = new NettyResponseStatus(future.getURI(), config, response); - HttpResponseHeaders responseHeaders = new NettyResponseHeaders(response.headers()); - - if (exitAfterProcessingFilters(channel, future, handler, status, responseHeaders)) { - return; - } - - future.setHttpHeaders(response.headers()); - if (exitAfterHandlingRedirect(channel, future, response, request, response.getStatus().code())) - return; - - boolean validStatus = response.getStatus().equals(SWITCHING_PROTOCOLS); - boolean validUpgrade = response.headers().get(HttpHeaders.Names.UPGRADE) != null; - String connection = response.headers().get(HttpHeaders.Names.CONNECTION); - if (connection == null) - connection = response.headers().get(HttpHeaders.Names.CONNECTION.toLowerCase(Locale.ENGLISH)); - boolean validConnection = HttpHeaders.Values.UPGRADE.equalsIgnoreCase(connection); - - status = new NettyResponseStatus(future.getURI(), config, response); - final boolean statusReceived = handler.onStatusReceived(status) == STATE.UPGRADE; - - if (!statusReceived) { - try { - handler.onCompleted(); - } finally { - future.done(); - } - return; - } - - final boolean headerOK = handler.onHeadersReceived(responseHeaders) == STATE.CONTINUE; - if (!headerOK || !validStatus || !validUpgrade || !validConnection) { - requestSender.abort(channel, future, new IOException("Invalid handshake response")); - return; - } - - String accept = response.headers().get(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT); - String key = getAcceptKey(future.getNettyRequest().getHttpRequest().headers().get(HttpHeaders.Names.SEC_WEBSOCKET_KEY)); - if (accept == null || !accept.equals(key)) { - requestSender.abort(channel, future, new IOException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, key))); - } - - channelManager.upgradePipelineForWebSockets(channel.pipeline()); - - invokeOnSucces(channel, handler); - future.done(); - - } else if (e instanceof WebSocketFrame) { - - final WebSocketFrame frame = (WebSocketFrame) e; - NettyWebSocket webSocket = NettyWebSocket.class.cast(handler.onCompleted()); - invokeOnSucces(channel, handler); - - if (webSocket != null) { - if (frame instanceof CloseWebSocketFrame) { - Channels.setDiscard(channel); - CloseWebSocketFrame closeFrame = CloseWebSocketFrame.class.cast(frame); - webSocket.onClose(closeFrame.statusCode(), closeFrame.reasonText()); - } else { - ByteBuf buf = frame.content(); - if (buf != null && buf.readableBytes() > 0) { - try { - NettyResponseBodyPart part = nettyConfig.getBodyPartFactory().newResponseBodyPart(buf, frame.isFinalFragment()); - handler.onBodyPartReceived(part); - - if (frame instanceof BinaryWebSocketFrame) { - webSocket.onBinaryFragment(part); - } else if (frame instanceof TextWebSocketFrame) { - webSocket.onTextFragment(part); - } else if (frame instanceof PingWebSocketFrame) { - webSocket.onPing(part); - } else if (frame instanceof PongWebSocketFrame) { - webSocket.onPong(part); - } - } finally { - buf.release(); - } - } - } - } else { - logger.debug("UpgradeHandler returned a null NettyWebSocket "); - } - } else { - logger.error("Invalid message {}", e); - } - } - - @Override - public void onError(NettyResponseFuture future, Throwable e) { - logger.warn("onError {}", e); - - try { - WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(future); - - NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); - if (webSocket != null) { - webSocket.onError(e.getCause()); - webSocket.close(); - } - } catch (Throwable t) { - logger.error("onError", t); - } - } - - @Override - public void onClose(NettyResponseFuture future) { - logger.trace("onClose {}"); - - try { - WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(future); - NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); - - logger.trace("Connection was closed abnormally (that is, with no close frame being sent)."); - if (webSocket != null) - webSocket.close(1006, "Connection was closed abnormally (that is, with no close frame being sent)."); - } catch (Throwable t) { - logger.error("onError", t); - } - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java deleted file mode 100755 index 1d6f43a37f..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; -import io.netty.handler.ssl.SslHandler; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; - -import java.net.ConnectException; -import java.nio.channels.ClosedChannelException; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLSession; - -import org.asynchttpclient.AsyncHandlerExtensions; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.channel.ChannelManager; -import org.asynchttpclient.providers.netty.channel.Channels; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.future.StackTraceInspector; -import org.asynchttpclient.util.Base64; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Non Blocking connect. - */ -final class NettyConnectListener implements ChannelFutureListener { - - private final static Logger LOGGER = LoggerFactory.getLogger(NettyConnectListener.class); - - private final AsyncHttpClientConfig config; - private final NettyRequestSender requestSender; - private final NettyResponseFuture future; - private final ChannelManager channelManager; - private final boolean channelPreempted; - private final String poolKey; - - public NettyConnectListener(AsyncHttpClientConfig config,// - NettyResponseFuture future,// - NettyRequestSender requestSender,// - ChannelManager channelManager,// - boolean channelPreempted,// - String poolKey) { - this.config = config; - this.future = future; - this.requestSender = requestSender; - this.channelManager = channelManager; - this.channelPreempted = channelPreempted; - this.poolKey = poolKey; - } - - private void abortChannelPreemption(String poolKey) { - if (channelPreempted) - channelManager.abortChannelPreemption(poolKey); - } - - private void writeRequest(Channel channel) { - - LOGGER.debug("Request using non cached Channel '{}':\n{}\n", channel, future.getNettyRequest().getHttpRequest()); - - if (future.isDone()) { - abortChannelPreemption(poolKey); - return; - } - - if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onConnectionOpen(); - - channelManager.registerOpenChannel(channel); - future.attachChannel(channel, false); - requestSender.writeRequest(future, channel); - } - - public void onFutureSuccess(final Channel channel) throws ConnectException { - Channels.setAttribute(channel, future); - final HostnameVerifier hostnameVerifier = config.getHostnameVerifier(); - final SslHandler sslHandler = ChannelManager.getSslHandler(channel.pipeline()); - if (hostnameVerifier != null && sslHandler != null) { - final String host = future.getURI().getHost(); - sslHandler.handshakeFuture().addListener(new GenericFutureListener>() { - @Override - public void operationComplete(Future handshakeFuture) throws Exception { - if (handshakeFuture.isSuccess()) { - Channel channel = (Channel) handshakeFuture.getNow(); - SSLEngine engine = sslHandler.engine(); - SSLSession session = engine.getSession(); - - LOGGER.debug("onFutureSuccess: session = {}, id = {}, isValid = {}, host = {}", session.toString(), - Base64.encode(session.getId()), session.isValid(), host); - if (hostnameVerifier.verify(host, session)) { - writeRequest(channel); - } else { - abortChannelPreemption(poolKey); - ConnectException exception = new ConnectException("HostnameVerifier exception"); - future.abort(exception); - throw exception; - } - } - } - }); - } else { - writeRequest(channel); - } - } - - public void onFutureFailure(Channel channel, Throwable cause) { - - abortChannelPreemption(poolKey); - - boolean canRetry = future.canRetry(); - LOGGER.debug("Trying to recover a dead cached channel {} with a retry value of {} ", channel, canRetry); - if (canRetry// - && cause != null// - && (cause instanceof ClosedChannelException || future.getState() != NettyResponseFuture.STATE.NEW || StackTraceInspector.abortOnDisconnectException(cause))) { - - if (requestSender.retry(future)) { - return; - } - } - - LOGGER.debug("Failed to recover from exception: {} with channel {}", cause, channel); - - boolean printCause = cause != null && cause.getMessage() != null; - String url = future.getURI().toUrl(); - String printedCause = printCause ? cause.getMessage() + " to " + url : url; - ConnectException e = new ConnectException(printedCause); - if (cause != null) - e.initCause(cause); - future.abort(e); - } - - public final void operationComplete(ChannelFuture f) throws Exception { - if (f.isSuccess()) - onFutureSuccess(f.channel()); - else - onFutureFailure(f.channel(), f.cause()); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequest.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequest.java deleted file mode 100755 index aec5d2fd03..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request; - -import org.asynchttpclient.providers.netty.request.body.NettyBody; - -import io.netty.handler.codec.http.HttpRequest; - -public final class NettyRequest { - - private final HttpRequest httpRequest; - private final NettyBody body; - - public NettyRequest(HttpRequest httpRequest, NettyBody body) { - this.httpRequest = httpRequest; - this.body = body; - } - - public HttpRequest getHttpRequest() { - return httpRequest; - } - - public NettyBody getBody() { - return body; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java deleted file mode 100755 index ac3368d5c3..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request; - -import static org.asynchttpclient.providers.netty.util.HttpUtils.isNTLM; -import static org.asynchttpclient.providers.netty.util.HttpUtils.isSecure; -import static org.asynchttpclient.providers.netty.util.HttpUtils.isWebSocket; -import static org.asynchttpclient.providers.netty.util.HttpUtils.useProxyConnect; -import static org.asynchttpclient.providers.netty.ws.WebSocketUtils.getKey; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.DEFAULT_CHARSET; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getAuthority; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getNonEmptyPath; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.keepAliveHeaderValue; -import static org.asynchttpclient.util.AuthenticatorUtils.computeBasicAuthentication; -import static org.asynchttpclient.util.AuthenticatorUtils.computeDigestAuthentication; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; -import io.netty.buffer.Unpooled; -import io.netty.handler.codec.http.DefaultFullHttpRequest; -import io.netty.handler.codec.http.DefaultHttpRequest; -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.HttpVersion; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.Map.Entry; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Param; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.cookie.CookieEncoder; -import org.asynchttpclient.generators.FileBodyGenerator; -import org.asynchttpclient.generators.InputStreamBodyGenerator; -import org.asynchttpclient.ntlm.NTLMEngine; -import org.asynchttpclient.ntlm.NTLMEngineException; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.providers.netty.request.body.NettyBody; -import org.asynchttpclient.providers.netty.request.body.NettyBodyBody; -import org.asynchttpclient.providers.netty.request.body.NettyByteArrayBody; -import org.asynchttpclient.providers.netty.request.body.NettyFileBody; -import org.asynchttpclient.providers.netty.request.body.NettyInputStreamBody; -import org.asynchttpclient.providers.netty.request.body.NettyMultipartBody; -import org.asynchttpclient.spnego.SpnegoEngine; -import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.UTF8UrlEncoder; - -public final class NettyRequestFactory { - - public static final String GZIP_DEFLATE = HttpHeaders.Values.GZIP + "," + HttpHeaders.Values.DEFLATE; - - private final AsyncHttpClientConfig config; - private final NettyAsyncHttpProviderConfig nettyConfig; - - public NettyRequestFactory(AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig) { - this.config = config; - this.nettyConfig = nettyConfig; - } - - private String requestUri(UriComponents uri, ProxyServer proxyServer, HttpMethod method) { - if (method == HttpMethod.CONNECT) - return getAuthority(uri); - - else if (proxyServer != null && !(useProxyConnect(uri) && config.isUseRelativeURIsWithConnectProxies())) - return uri.toString(); - - else { - String path = getNonEmptyPath(uri); - if (isNonEmpty(uri.getQuery())) - return path + "?" + uri.getQuery(); - else - return path; - } - } - - private String hostHeader(Request request, UriComponents uri) { - String host = request.getVirtualHost() != null ? request.getVirtualHost() : uri.getHost(); - return request.getVirtualHost() != null || uri.getPort() == -1 ? host : host + ":" + uri.getPort(); - } - - private String authorizationHeader(Request request, UriComponents uri, ProxyServer proxyServer, Realm realm) throws IOException { - - String authorizationHeader = null; - - if (realm != null && realm.getUsePreemptiveAuth()) { - - switch (realm.getAuthScheme()) { - case BASIC: - authorizationHeader = computeBasicAuthentication(realm); - break; - case DIGEST: - if (isNonEmpty(realm.getNonce())) { - try { - authorizationHeader = computeDigestAuthentication(realm); - } catch (NoSuchAlgorithmException e) { - throw new SecurityException(e); - } - } - break; - case NTLM: - String domain; - if (proxyServer != null && proxyServer.getNtlmDomain() != null) { - domain = proxyServer.getNtlmDomain(); - } else { - domain = realm.getNtlmDomain(); - } - try { - String msg = NTLMEngine.INSTANCE.generateType1Msg("NTLM " + domain, realm.getNtlmHost()); - authorizationHeader = "NTLM " + msg; - } catch (NTLMEngineException e) { - throw new IOException(e); - } - break; - case KERBEROS: - case SPNEGO: - - String host; - if (proxyServer != null) - host = proxyServer.getHost(); - else if (request.getVirtualHost() != null) - host = request.getVirtualHost(); - else - host = uri.getHost(); - - try { - authorizationHeader = "Negotiate " + SpnegoEngine.instance().generateToken(host); - } catch (Throwable e) { - throw new IOException(e); - } - break; - case NONE: - break; - default: - throw new IllegalStateException("Invalid Authentication " + realm); - } - } - - return authorizationHeader; - } - - private String proxyAuthorizationHeader(Request request, ProxyServer proxyServer, HttpMethod method) throws IOException { - - String proxyAuthorization = null; - - if (method == HttpMethod.CONNECT) { - List auth = request.getHeaders().get(HttpHeaders.Names.PROXY_AUTHORIZATION); - if (isNTLM(auth)) { - proxyAuthorization = auth.get(0); - } - - } else if (proxyServer != null && proxyServer.getPrincipal() != null) { - if (isNonEmpty(proxyServer.getNtlmDomain())) { - List auth = request.getHeaders().get(HttpHeaders.Names.PROXY_AUTHORIZATION); - if (!isNTLM(auth)) { - try { - String msg = NTLMEngine.INSTANCE.generateType1Msg(proxyServer.getNtlmDomain(), proxyServer.getHost()); - proxyAuthorization = "NTLM " + msg; - } catch (NTLMEngineException e) { - IOException ie = new IOException(); - ie.initCause(e); - throw ie; - } - } - } else { - proxyAuthorization = computeBasicAuthentication(proxyServer); - } - } - - return proxyAuthorization; - } - - private byte[] computeBodyFromParams(List params, Charset bodyCharset) { - - StringBuilder sb = new StringBuilder(); - for (Param param : params) { - UTF8UrlEncoder.appendEncoded(sb, param.getName()); - sb.append('='); - UTF8UrlEncoder.appendEncoded(sb, param.getValue()); - sb.append('&'); - } - sb.setLength(sb.length() - 1); - return sb.toString().getBytes(bodyCharset); - } - - private NettyBody body(Request request, HttpMethod method) throws IOException { - NettyBody nettyBody = null; - if (method != HttpMethod.CONNECT) { - - Charset bodyCharset = request.getBodyEncoding() == null ? DEFAULT_CHARSET : Charset.forName(request.getBodyEncoding()); - - if (request.getByteData() != null) { - nettyBody = new NettyByteArrayBody(request.getByteData()); - - } else if (request.getStringData() != null) { - nettyBody = new NettyByteArrayBody(request.getStringData().getBytes(bodyCharset)); - - } else if (request.getStreamData() != null) { - nettyBody = new NettyInputStreamBody(request.getStreamData()); - - } else if (isNonEmpty(request.getFormParams())) { - - String contentType = null; - if (!request.getHeaders().containsKey(HttpHeaders.Names.CONTENT_TYPE)) - contentType = HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED; - - nettyBody = new NettyByteArrayBody(computeBodyFromParams(request.getFormParams(), bodyCharset), contentType); - - } else if (isNonEmpty(request.getParts())) { - nettyBody = new NettyMultipartBody(request.getParts(), request.getHeaders(), nettyConfig); - - } else if (request.getFile() != null) { - nettyBody = new NettyFileBody(request.getFile(), nettyConfig); - - } else if (request.getBodyGenerator() instanceof FileBodyGenerator) { - FileBodyGenerator fileBodyGenerator = (FileBodyGenerator) request.getBodyGenerator(); - nettyBody = new NettyFileBody(fileBodyGenerator.getFile(), fileBodyGenerator.getRegionSeek(), fileBodyGenerator.getRegionLength(), nettyConfig); - - } else if (request.getBodyGenerator() instanceof InputStreamBodyGenerator) { - nettyBody = new NettyInputStreamBody(InputStreamBodyGenerator.class.cast(request.getBodyGenerator()).getInputStream()); - - } else if (request.getBodyGenerator() != null) { - nettyBody = new NettyBodyBody(request.getBodyGenerator().createBody(), nettyConfig); - } - } - - return nettyBody; - } - - public NettyRequest newNettyRequest(Request request, UriComponents uri, boolean forceConnect, ProxyServer proxyServer) throws IOException { - - HttpMethod method = forceConnect ? HttpMethod.CONNECT : HttpMethod.valueOf(request.getMethod()); - HttpVersion httpVersion = method == HttpMethod.CONNECT ? HttpVersion.HTTP_1_0 : HttpVersion.HTTP_1_1; - String requestUri = requestUri(uri, proxyServer, method); - - NettyBody body = body(request, method); - - HttpRequest httpRequest; - NettyRequest nettyRequest; - if (body instanceof NettyByteArrayBody) { - byte[] bytes = NettyByteArrayBody.class.cast(body).getBytes(); - httpRequest = new DefaultFullHttpRequest(httpVersion, method, requestUri, Unpooled.wrappedBuffer(bytes)); - // body is passed as null as it's written directly with the request - nettyRequest = new NettyRequest(httpRequest, null); - - } else if (body == null) { - httpRequest = new DefaultFullHttpRequest(httpVersion, method, requestUri); - nettyRequest = new NettyRequest(httpRequest, null); - - } else { - httpRequest = new DefaultHttpRequest(httpVersion, method, requestUri); - nettyRequest = new NettyRequest(httpRequest, body); - } - - HttpHeaders headers = httpRequest.headers(); - - if (method != HttpMethod.CONNECT) { - // assign headers as configured on request - for (Entry> header : request.getHeaders()) { - headers.set(header.getKey(), header.getValue()); - } - - if (isNonEmpty(request.getCookies())) - headers.set(HttpHeaders.Names.COOKIE, CookieEncoder.encode(request.getCookies())); - - if (config.isCompressionEnabled()) - headers.set(HttpHeaders.Names.ACCEPT_ENCODING, GZIP_DEFLATE); - } - - if (body != null) { - if (body.getContentLength() < 0) - headers.set(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); - else - headers.set(HttpHeaders.Names.CONTENT_LENGTH, body.getContentLength()); - - if (body.getContentType() != null) - headers.set(HttpHeaders.Names.CONTENT_TYPE, body.getContentType()); - } - - // connection header and friends - boolean webSocket = isWebSocket(uri.getScheme()); - if (method != HttpMethod.CONNECT && webSocket) { - headers.set(HttpHeaders.Names.UPGRADE, HttpHeaders.Values.WEBSOCKET)// - .set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.UPGRADE)// - .set(HttpHeaders.Names.ORIGIN, "http://" + uri.getHost() + ":" + (uri.getPort() == -1 ? isSecure(uri.getScheme()) ? 443 : 80 : uri.getPort()))// - .set(HttpHeaders.Names.SEC_WEBSOCKET_KEY, getKey())// - .set(HttpHeaders.Names.SEC_WEBSOCKET_VERSION, "13"); - - } else if (!headers.contains(HttpHeaders.Names.CONNECTION)) { - headers.set(HttpHeaders.Names.CONNECTION, keepAliveHeaderValue(config)); - } - - String hostHeader = hostHeader(request, uri); - if (hostHeader != null) - headers.set(HttpHeaders.Names.HOST, hostHeader); - - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - String authorizationHeader = authorizationHeader(request, uri, proxyServer, realm); - if (authorizationHeader != null) - // don't override authorization but append - headers.add(HttpHeaders.Names.AUTHORIZATION, authorizationHeader); - - String proxyAuthorizationHeader = proxyAuthorizationHeader(request, proxyServer, method); - if (proxyAuthorizationHeader != null) - headers.set(HttpHeaders.Names.PROXY_AUTHORIZATION, proxyAuthorizationHeader); - - // Add default accept headers - if (!headers.contains(HttpHeaders.Names.ACCEPT)) - headers.set(HttpHeaders.Names.ACCEPT, "*/*"); - - // Add default user agent - if (!headers.contains(HttpHeaders.Names.USER_AGENT) && config.getUserAgent() != null) - headers.set(HttpHeaders.Names.USER_AGENT, config.getUserAgent()); - - return nettyRequest; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java deleted file mode 100755 index bf9bd5e0e3..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request; - -import static org.asynchttpclient.providers.netty.util.HttpUtils.WEBSOCKET; -import static org.asynchttpclient.providers.netty.util.HttpUtils.isSecure; -import static org.asynchttpclient.providers.netty.util.HttpUtils.useProxyConnect; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getDefaultPort; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.requestTimeout; -import static org.asynchttpclient.util.ProxyUtils.avoidProxy; -import static org.asynchttpclient.util.ProxyUtils.getProxyServer; -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.util.Timeout; -import io.netty.util.Timer; -import io.netty.util.TimerTask; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHandlerExtensions; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ConnectionPoolKeyStrategy; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Request; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.IOExceptionFilter; -import org.asynchttpclient.listener.TransferCompletionHandler; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.providers.netty.channel.ChannelManager; -import org.asynchttpclient.providers.netty.channel.Channels; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.request.timeout.ReadTimeoutTimerTask; -import org.asynchttpclient.providers.netty.request.timeout.RequestTimeoutTimerTask; -import org.asynchttpclient.providers.netty.request.timeout.TimeoutsHolder; -import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.websocket.WebSocketUpgradeHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class NettyRequestSender { - - private static final Logger LOGGER = LoggerFactory.getLogger(NettyRequestSender.class); - - private final AsyncHttpClientConfig config; - private final ChannelManager channelManager; - private final Timer nettyTimer; - private final AtomicBoolean closed; - private final NettyRequestFactory requestFactory; - - public NettyRequestSender(AsyncHttpClientConfig config,// - NettyAsyncHttpProviderConfig nettyConfig,// - ChannelManager channelManager,// - Timer nettyTimer,// - AtomicBoolean closed) { - this.config = config; - this.channelManager = channelManager; - this.nettyTimer = nettyTimer; - this.closed = closed; - requestFactory = new NettyRequestFactory(config, nettyConfig); - } - - public ListenableFuture sendRequest(final Request request,// - final AsyncHandler asyncHandler,// - NettyResponseFuture future,// - boolean reclaimCache) throws IOException { - - if (closed.get()) - throw new IOException("Closed"); - - UriComponents uri = request.getURI(); - - // FIXME really useful? Why not do this check when building the request? - if (uri.getScheme().startsWith(WEBSOCKET) && !validateWebSocketRequest(request, asyncHandler)) - throw new IOException("WebSocket method must be a GET"); - - ProxyServer proxyServer = getProxyServer(config, request); - boolean resultOfAConnect = future != null && future.getNettyRequest() != null && future.getNettyRequest().getHttpRequest().getMethod() == HttpMethod.CONNECT; - boolean useProxy = proxyServer != null && !resultOfAConnect; - - if (useProxy && useProxyConnect(uri)) - // SSL proxy, have to handle CONNECT - if (future != null && future.isConnectAllowed()) - // CONNECT forced - return sendRequestWithCertainForceConnect(request, asyncHandler, future, reclaimCache, uri, proxyServer, true, true); - else - return sendRequestThroughSslProxy(request, asyncHandler, future, reclaimCache, uri, proxyServer); - else - return sendRequestWithCertainForceConnect(request, asyncHandler, future, reclaimCache, uri, proxyServer, useProxy, false); - } - - /** - * We know for sure if we have to force to connect or not, so we can build - * the HttpRequest right away This reduces the probability of having a - * pooled channel closed by the server by the time we build the request - */ - private ListenableFuture sendRequestWithCertainForceConnect(// - Request request,// - AsyncHandler asyncHandler,// - NettyResponseFuture future,// - boolean reclaimCache,// - UriComponents uri,// - ProxyServer proxyServer,// - boolean useProxy,// - boolean forceConnect) throws IOException { - - NettyResponseFuture newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, uri, proxyServer, forceConnect); - - Channel channel = getCachedChannel(future, uri, request.getConnectionPoolKeyStrategy(), proxyServer, asyncHandler); - - if (Channels.isChannelValid(channel)) - return sendRequestWithCachedChannel(request, uri, proxyServer, newFuture, asyncHandler, channel); - else - return sendRequestWithNewChannel(request, uri, proxyServer, useProxy, newFuture, asyncHandler, reclaimCache); - } - - /** - * 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 - * request is built - */ - @SuppressWarnings("unused") - private ListenableFuture sendRequestThroughSslProxy(// - Request request,// - AsyncHandler asyncHandler,// - NettyResponseFuture future,// - boolean reclaimCache,// - UriComponents uri,// - ProxyServer proxyServer) throws IOException { - - NettyResponseFuture newFuture = null; - for (int i = 0; i < 3; i++) { - Channel channel = getCachedChannel(future, uri, request.getConnectionPoolKeyStrategy(), proxyServer, asyncHandler); - if (Channels.isChannelValid(channel)) - if (newFuture == null) - newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, uri, proxyServer, false); - - if (Channels.isChannelValid(channel)) - // if the channel is still active, we can use it, otherwise try gain - return sendRequestWithCachedChannel(request, uri, proxyServer, newFuture, asyncHandler, channel); - else - // pool is empty - break; - } - - newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, uri, proxyServer, true); - return sendRequestWithNewChannel(request, uri, proxyServer, true, newFuture, asyncHandler, reclaimCache); - } - - private NettyResponseFuture newNettyRequestAndResponseFuture(final Request request, final AsyncHandler asyncHandler, NettyResponseFuture originalFuture, - UriComponents uri, ProxyServer proxy, boolean forceConnect) throws IOException { - - NettyRequest nettyRequest = requestFactory.newNettyRequest(request, uri, forceConnect, proxy); - - if (originalFuture == null) { - return newNettyResponseFuture(uri, request, asyncHandler, nettyRequest, proxy); - } else { - originalFuture.setNettyRequest(nettyRequest); - originalFuture.setRequest(request); - return originalFuture; - } - } - - private Channel getCachedChannel(NettyResponseFuture future, UriComponents uri, ConnectionPoolKeyStrategy poolKeyGen, ProxyServer proxyServer, AsyncHandler asyncHandler) { - - if (future != null && future.reuseChannel() && Channels.isChannelValid(future.channel())) - return future.channel(); - else - return pollAndVerifyCachedChannel(uri, proxyServer, poolKeyGen, asyncHandler); - } - - private ListenableFuture sendRequestWithCachedChannel(Request request, UriComponents uri, ProxyServer proxy, NettyResponseFuture future, - AsyncHandler asyncHandler, Channel channel) throws IOException { - - if (asyncHandler instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(asyncHandler).onConnectionPooled(); - - future.setState(NettyResponseFuture.STATE.POOLED); - future.attachChannel(channel, false); - - LOGGER.debug("\nUsing cached Channel {}\n for request \n{}\n", channel, future.getNettyRequest().getHttpRequest()); - Channels.setAttribute(channel, future); - - try { - writeRequest(future, channel); - } catch (Exception ex) { - LOGGER.debug("writeRequest failure", ex); - if (ex.getMessage() != null && ex.getMessage().contains("SSLEngine")) { - LOGGER.debug("SSLEngine failure", ex); - future = null; - } else { - try { - asyncHandler.onThrowable(ex); - } catch (Throwable t) { - LOGGER.warn("doConnect.writeRequest()", t); - } - IOException ioe = new IOException(ex.getMessage()); - ioe.initCause(ex); - throw ioe; - } - } - return future; - } - - private ListenableFuture sendRequestWithNewChannel(// - Request request,// - UriComponents uri,// - ProxyServer proxy,// - boolean useProxy,// - NettyResponseFuture future,// - AsyncHandler asyncHandler,// - boolean reclaimCache) throws IOException { - - boolean useSSl = isSecure(uri) && !useProxy; - - // Do not throw an exception when we need an extra connection for a - // redirect - // FIXME why? This violate the max connection per host handling, right? - Bootstrap bootstrap = channelManager.getBootstrap(request.getURI(), useProxy, useSSl); - - boolean channelPreempted = false; - String poolKey = null; - - // Do not throw an exception when we need an extra connection for a - // redirect. - if (!reclaimCache) { - - // only compute when maxConnectionPerHost is enabled - // FIXME clean up - if (config.getMaxConnectionsPerHost() > 0) - poolKey = channelManager.getPoolKey(future); - - channelPreempted = preemptChannel(asyncHandler, poolKey); - } - - if (asyncHandler instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(asyncHandler).onOpenConnection(); - - try { - ChannelFuture channelFuture = connect(request, uri, proxy, useProxy, bootstrap); - channelFuture.addListener(new NettyConnectListener(config, future, this, channelManager, channelPreempted, poolKey)); - - } catch (Throwable t) { - if (channelPreempted) - channelManager.abortChannelPreemption(poolKey); - - abort(null, future, t.getCause() == null ? t : t.getCause()); - } - - return future; - } - - private NettyResponseFuture newNettyResponseFuture(UriComponents uri, Request request, AsyncHandler asyncHandler, NettyRequest nettyRequest, ProxyServer proxyServer) { - - NettyResponseFuture future = new NettyResponseFuture(// - uri,// - request,// - asyncHandler,// - nettyRequest,// - config.getMaxRequestRetry(),// - request.getConnectionPoolKeyStrategy(),// - proxyServer); - - String expectHeader = request.getHeaders().getFirstValue(HttpHeaders.Names.EXPECT); - if (expectHeader != null && expectHeader.equalsIgnoreCase(HttpHeaders.Values.CONTINUE)) - future.setDontWriteBodyBecauseExpectContinue(true); - return future; - } - - public void writeRequest(NettyResponseFuture future, Channel channel) { - try { - // if the channel is dead because it was pooled and the remote - // server decided to close it, - // we just let it go and the channelInactive do its work - if (!Channels.isChannelValid(channel)) - return; - - NettyRequest nettyRequest = future.getNettyRequest(); - HttpRequest httpRequest = nettyRequest.getHttpRequest(); - AsyncHandler handler = future.getAsyncHandler(); - - if (handler instanceof TransferCompletionHandler) - configureTransferAdapter(handler, httpRequest); - - if (!future.isHeadersAlreadyWrittenOnContinue()) { - try { - if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onSendRequest(); - - channel.writeAndFlush(httpRequest, channel.newProgressivePromise()).addListener(new ProgressListener(config, future.getAsyncHandler(), future, true, 0L)); - } catch (Throwable cause) { - // FIXME why not notify? - LOGGER.debug(cause.getMessage(), cause); - try { - channel.close(); - } catch (RuntimeException ex) { - LOGGER.debug(ex.getMessage(), ex); - } - return; - } - } - - if (!future.isDontWriteBodyBecauseExpectContinue() && !httpRequest.getMethod().equals(HttpMethod.CONNECT) && nettyRequest.getBody() != null) - nettyRequest.getBody().write(channel, future, config); - - } catch (Throwable ioe) { - try { - channel.close(); - } catch (RuntimeException ex) { - LOGGER.debug(ex.getMessage(), ex); - } - } - - scheduleTimeouts(future); - } - - private InetSocketAddress remoteAddress(Request request, UriComponents uri, ProxyServer proxy, boolean useProxy) { - if (request.getInetAddress() != null) - return new InetSocketAddress(request.getInetAddress(), getDefaultPort(uri)); - - else if (!useProxy || avoidProxy(proxy, uri.getHost())) - return new InetSocketAddress(uri.getHost(), getDefaultPort(uri)); - - else - return new InetSocketAddress(proxy.getHost(), proxy.getPort()); - } - - private ChannelFuture connect(Request request, UriComponents uri, ProxyServer proxy, boolean useProxy, Bootstrap bootstrap) { - InetSocketAddress remoteAddress = remoteAddress(request, uri, proxy, useProxy); - - if (request.getLocalAddress() != null) - return bootstrap.connect(remoteAddress, new InetSocketAddress(request.getLocalAddress(), 0)); - else - return bootstrap.connect(remoteAddress); - } - - private void configureTransferAdapter(AsyncHandler handler, HttpRequest httpRequest) { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - for (Map.Entry entries : httpRequest.headers()) { - h.add(entries.getKey(), entries.getValue()); - } - - TransferCompletionHandler.class.cast(handler).headers(h); - } - - private void scheduleTimeouts(NettyResponseFuture nettyResponseFuture) { - - nettyResponseFuture.touch(); - int requestTimeoutInMs = requestTimeout(config, nettyResponseFuture.getRequest()); - TimeoutsHolder timeoutsHolder = new TimeoutsHolder(); - if (requestTimeoutInMs != -1) { - Timeout requestTimeout = newTimeout(new RequestTimeoutTimerTask(nettyResponseFuture, this, timeoutsHolder, requestTimeoutInMs), requestTimeoutInMs); - timeoutsHolder.requestTimeout = requestTimeout; - } - - int readTimeout = config.getReadTimeout(); - if (readTimeout != -1 && readTimeout < requestTimeoutInMs) { - // no need for a idleConnectionTimeout that's less than the - // requestTimeoutInMs - Timeout idleConnectionTimeout = newTimeout(new ReadTimeoutTimerTask(nettyResponseFuture, this, timeoutsHolder, requestTimeoutInMs, readTimeout), readTimeout); - timeoutsHolder.readTimeout = idleConnectionTimeout; - } - nettyResponseFuture.setTimeoutsHolder(timeoutsHolder); - } - - public Timeout newTimeout(TimerTask task, long delay) { - return nettyTimer.newTimeout(task, delay, TimeUnit.MILLISECONDS); - } - - public void abort(Channel channel, NettyResponseFuture future, Throwable t) { - - if (channel != null) - channelManager.closeChannel(channel); - - if (!future.isDone()) { - LOGGER.debug("Aborting Future {}\n", future); - LOGGER.debug(t.getMessage(), t); - future.abort(t); - } - } - - public boolean retry(NettyResponseFuture future) { - - if (isClosed()) - return false; - - // FIXME what is this for??? - //channelManager.removeAll(channel); - - if (future.canBeReplayed()) { - future.setState(NettyResponseFuture.STATE.RECONNECTED); - future.getAndSetStatusReceived(false); - - LOGGER.debug("Trying to recover request {}\n", future.getNettyRequest().getHttpRequest()); - if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) { - AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onRetry(); - } - - try { - sendNextRequest(future.getRequest(), future); - return true; - - } catch (IOException iox) { - future.setState(NettyResponseFuture.STATE.CLOSED); - future.abort(iox); - LOGGER.error("Remotely Closed, unable to recover", iox); - return false; - } - } else { - LOGGER.debug("Unable to recover future {}\n", future); - return false; - } - } - - public boolean applyIoExceptionFiltersAndReplayRequest(NettyResponseFuture future, IOException e, Channel channel) throws IOException { - - boolean replayed = false; - - @SuppressWarnings({ "unchecked", "rawtypes" }) - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(future.getAsyncHandler()).request(future.getRequest()).ioException(e).build(); - for (IOExceptionFilter asyncFilter : config.getIOExceptionFilters()) { - try { - fc = asyncFilter.filter(fc); - if (fc == null) { - throw new NullPointerException("FilterContext is null"); - } - } catch (FilterException efe) { - abort(channel, future, efe); - } - } - - if (fc.replayRequest() && future.canBeReplayed()) { - replayRequest(future, fc, channel); - replayed = true; - } - return replayed; - } - - public void sendNextRequest(final Request request, final NettyResponseFuture future) throws IOException { - sendRequest(request, future.getAsyncHandler(), future, true); - } - - // FIXME is this useful? Can't we do that when building the request? - private boolean validateWebSocketRequest(Request request, AsyncHandler asyncHandler) { - return request.getMethod().equals(HttpMethod.GET.name()) && asyncHandler instanceof WebSocketUpgradeHandler; - } - - private Channel pollAndVerifyCachedChannel(UriComponents uri, ProxyServer proxy, ConnectionPoolKeyStrategy connectionPoolKeyStrategy, AsyncHandler asyncHandler) { - - if (asyncHandler instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(asyncHandler).onPoolConnection(); - - final Channel channel = channelManager.poll(connectionPoolKeyStrategy.getKey(uri, proxy)); - - if (channel != null) { - LOGGER.debug("Using cached Channel {}\n for uri {}\n", channel, uri); - - try { - channelManager.verifyChannelPipeline(channel.pipeline(), uri.getScheme()); - } catch (Exception ex) { - LOGGER.debug(ex.getMessage(), ex); - } - } - return channel; - } - - private boolean preemptChannel(AsyncHandler asyncHandler, String poolKey) throws IOException { - - boolean channelPreempted = false; - if (channelManager.preemptChannel(poolKey)) { - channelPreempted = true; - } else { - IOException ex = new IOException(String.format("Too many connections %s", config.getMaxConnections())); - try { - asyncHandler.onThrowable(ex); - } catch (Exception e) { - LOGGER.warn("asyncHandler.onThrowable crashed", e); - } - throw ex; - } - return channelPreempted; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void replayRequest(final NettyResponseFuture future, FilterContext fc, Channel channel) throws IOException { - - Request newRequest = fc.getRequest(); - future.setAsyncHandler(fc.getAsyncHandler()); - future.setState(NettyResponseFuture.STATE.NEW); - future.touch(); - - LOGGER.debug("\n\nReplaying Request {}\n for Future {}\n", newRequest, future); - if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onRetry(); - - channelManager.drainChannel(channel, future); - sendNextRequest(newRequest, future); - } - - public boolean isClosed() { - return closed.get(); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java deleted file mode 100755 index 8e1b7fe2cd..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ProgressAsyncHandler; -import org.asynchttpclient.Realm; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.future.StackTraceInspector; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelProgressiveFuture; -import io.netty.channel.ChannelProgressiveFutureListener; - -import java.nio.channels.ClosedChannelException; - -public class ProgressListener implements ChannelProgressiveFutureListener { - - private static final Logger LOGGER = LoggerFactory.getLogger(ProgressListener.class); - - private final AsyncHttpClientConfig config; - private final AsyncHandler asyncHandler; - private final NettyResponseFuture future; - private final boolean notifyHeaders; - private final long expectedTotal; - private long lastProgress = 0L; - - public ProgressListener(AsyncHttpClientConfig config,// - AsyncHandler asyncHandler,// - NettyResponseFuture future,// - boolean notifyHeaders,// - long expectedTotal) { - this.config = config; - this.asyncHandler = asyncHandler; - this.future = future; - this.notifyHeaders = notifyHeaders; - this.expectedTotal = expectedTotal; - } - - private boolean abortOnThrowable(Throwable cause, Channel channel) { - - if (cause != null && future.getState() != NettyResponseFuture.STATE.NEW) { - - if (cause instanceof IllegalStateException) { - LOGGER.debug(cause.getMessage(), cause); - try { - channel.close(); - } catch (RuntimeException ex) { - LOGGER.debug(ex.getMessage(), ex); - } - } else if (cause instanceof ClosedChannelException || StackTraceInspector.abortOnReadOrWriteException(cause)) { - - if (LOGGER.isDebugEnabled()) - LOGGER.debug(cause.getMessage(), cause); - - try { - channel.close(); - } catch (RuntimeException ex) { - LOGGER.debug(ex.getMessage(), ex); - } - } else { - future.abort(cause); - } - - return true; - } - - return false; - } - - @Override - public void operationComplete(ChannelProgressiveFuture cf) { - // The write operation failed. If the channel was cached, it means it - // got asynchronously closed. - // Let's retry a second time. - - if (!abortOnThrowable(cf.cause(), cf.channel())) { - - future.touch(); - - /** - * We need to make sure we aren't in the middle of an authorization - * process before publishing events as we will re-publish again the - * same event after the authorization, causing unpredictable - * behavior. - */ - Realm realm = future.getRequest().getRealm() != null ? future.getRequest().getRealm() : config.getRealm(); - boolean startPublishing = future.isInAuth() || realm == null || realm.getUsePreemptiveAuth(); - - if (startPublishing && asyncHandler instanceof ProgressAsyncHandler) { - ProgressAsyncHandler progressAsyncHandler = (ProgressAsyncHandler) asyncHandler; - if (notifyHeaders) { - progressAsyncHandler.onHeaderWriteCompleted(); - } else { - progressAsyncHandler.onContentWriteCompleted(); - } - } - } - } - - @Override - public void operationProgressed(ChannelProgressiveFuture f, long progress, long total) { - future.touch(); - if (!notifyHeaders && asyncHandler instanceof ProgressAsyncHandler) { - long lastLastProgress = lastProgress; - lastProgress = progress; - if (total < 0) - total = expectedTotal; - ProgressAsyncHandler.class.cast(asyncHandler).onContentWriteProgress(progress - lastLastProgress, progress, total); - } - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java deleted file mode 100755 index a9b50bd404..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request.body; - -import org.asynchttpclient.Body; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.stream.ChunkedInput; - -import java.nio.ByteBuffer; - -/** - * Adapts a {@link Body} to Netty's {@link ChunkedInput}. - */ -public class BodyChunkedInput implements ChunkedInput { - - private static final int DEFAULT_CHUNK_SIZE = 8 * 1024; - - private final Body body; - private final int contentLength; - private final int chunkSize; - - private boolean endOfInput; - - public BodyChunkedInput(Body body) { - if (body == null) - throw new NullPointerException("body"); - this.body = body; - contentLength = (int) body.getContentLength(); - if (contentLength <= 0) - chunkSize = DEFAULT_CHUNK_SIZE; - else - chunkSize = Math.min(contentLength, DEFAULT_CHUNK_SIZE); - } - - @Override - public ByteBuf readChunk(ChannelHandlerContext ctx) throws Exception { - if (endOfInput) { - return null; - } else { - // FIXME pass a visitor so we can directly pass a pooled ByteBuf - ByteBuffer buffer = ByteBuffer.allocate(chunkSize); - long r = body.read(buffer); - if (r < 0L) { - endOfInput = true; - return null; - } else { - endOfInput = r == contentLength || r < chunkSize && contentLength > 0; - buffer.flip(); - return Unpooled.wrappedBuffer(buffer); - } - } - } - - @Override - public boolean isEndOfInput() throws Exception { - return endOfInput; - } - - @Override - public void close() throws Exception { - body.close(); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java deleted file mode 100755 index b1d02275e4..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request.body; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; - - -import org.asynchttpclient.RandomAccessBody; - -import io.netty.channel.FileRegion; -import io.netty.util.AbstractReferenceCounted; - -import java.io.IOException; -import java.nio.channels.WritableByteChannel; -/** - * Adapts a {@link RandomAccessBody} to Netty's {@link FileRegion}. - */ -public class BodyFileRegion extends AbstractReferenceCounted implements FileRegion { - - private final RandomAccessBody body; - private long transfered; - - public BodyFileRegion(RandomAccessBody body) { - if (body == null) - throw new NullPointerException("body"); - this.body = body; - } - - @Override - public long position() { - return 0; - } - - @Override - public long count() { - return body.getContentLength(); - } - - @Override - public long transfered() { - return transfered; - } - - @Override - public long transferTo(WritableByteChannel target, long position) throws IOException { - long written = body.transferTo(position, target); - if (written > 0) { - transfered += written; - } - return written; - } - - @Override - protected void deallocate() { - closeSilently(body); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/FeedableBodyGenerator.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/FeedableBodyGenerator.java deleted file mode 100755 index dbff022523..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/FeedableBodyGenerator.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request.body; - -import org.asynchttpclient.Body; -import org.asynchttpclient.BodyGenerator; -import org.asynchttpclient.util.StandardCharsets; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * {@link BodyGenerator} which may return just part of the payload at the time handler is requesting it. - * If it happens, PartialBodyGenerator becomes responsible for finishing payload transferring asynchronously. - */ -public class FeedableBodyGenerator implements BodyGenerator { - private final static byte[] END_PADDING = "\r\n".getBytes(StandardCharsets.US_ASCII); - private final static byte[] ZERO = "0".getBytes(StandardCharsets.US_ASCII); - private final Queue queue = new ConcurrentLinkedQueue(); - private final AtomicInteger queueSize = new AtomicInteger(); - private FeedListener listener; - - @Override - public Body createBody() throws IOException { - return new PushBody(); - } - - public void feed(final ByteBuffer buffer, final boolean isLast) throws IOException { - queue.offer(new BodyPart(buffer, isLast)); - queueSize.incrementAndGet(); - if (listener != null) { - listener.onContentAdded(); - } - } - - public static interface FeedListener { - void onContentAdded(); - } - - public void setListener(FeedListener listener) { - this.listener = listener; - } - - private final class PushBody implements Body { - private final int ONGOING = 0; - private final int CLOSING = 1; - private final int FINISHED = 2; - - private int finishState = 0; - - @Override - public long getContentLength() { - return -1; - } - - @Override - public long read(final ByteBuffer buffer) throws IOException { - BodyPart nextPart = queue.peek(); - if (nextPart == null) { - // Nothing in the queue - switch (finishState) { - case ONGOING: - return 0; - case CLOSING: - buffer.put(ZERO); - buffer.put(END_PADDING); - finishState = FINISHED; - return buffer.position(); - case FINISHED: - buffer.put(END_PADDING); - return -1; - } - } - int capacity = buffer.remaining() - 10; // be safe (we'll have to add size, ending, etc.) - int size = Math.min(nextPart.buffer.remaining(), capacity); - buffer.put(Integer.toHexString(size).getBytes(StandardCharsets.US_ASCII)); - buffer.put(END_PADDING); - for (int i = 0; i < size; i++) { - buffer.put(nextPart.buffer.get()); - } - buffer.put(END_PADDING); - if (!nextPart.buffer.hasRemaining()) { - if (nextPart.isLast) { - finishState = CLOSING; - } - queue.remove(); - } - return size; - } - - @Override - public void close() throws IOException { - } - - } - - private final static class BodyPart { - private final boolean isLast; - private final ByteBuffer buffer; - - public BodyPart(final ByteBuffer buffer, final boolean isLast) { - this.buffer = buffer; - this.isLast = isLast; - } - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBody.java deleted file mode 100755 index cea79857c3..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBody.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request.body; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; - -import io.netty.channel.Channel; - -import java.io.IOException; - -public interface NettyBody { - - long getContentLength(); - - String getContentType(); - - void write(Channel channel, NettyResponseFuture future, AsyncHttpClientConfig config) throws IOException; -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java deleted file mode 100755 index 96957591ab..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request.body; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelProgressiveFuture; -import io.netty.handler.codec.http.LastHttpContent; -import io.netty.handler.stream.ChunkedWriteHandler; - -import java.io.IOException; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Body; -import org.asynchttpclient.BodyGenerator; -import org.asynchttpclient.RandomAccessBody; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.providers.netty.channel.ChannelManager; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.request.ProgressListener; -import org.asynchttpclient.providers.netty.request.body.FeedableBodyGenerator.FeedListener; - -public class NettyBodyBody implements NettyBody { - - private final Body body; - private final NettyAsyncHttpProviderConfig nettyConfig; - - public NettyBodyBody(Body body, NettyAsyncHttpProviderConfig nettyConfig) { - this.body = body; - this.nettyConfig = nettyConfig; - } - - public Body getBody() { - return body; - } - - @Override - public long getContentLength() { - return body.getContentLength(); - } - - @Override - public String getContentType() { - return null; - }; - - @Override - public void write(final Channel channel, NettyResponseFuture future, AsyncHttpClientConfig config) throws IOException { - - Object msg; - if (body instanceof RandomAccessBody && !ChannelManager.isSslHandlerConfigured(channel.pipeline()) && !nettyConfig.isDisableZeroCopy()) { - msg = new BodyFileRegion((RandomAccessBody) body); - - } else { - msg = new BodyChunkedInput(body); - - BodyGenerator bg = future.getRequest().getBodyGenerator(); - if (bg instanceof FeedableBodyGenerator) { - FeedableBodyGenerator.class.cast(bg).setListener(new FeedListener() { - @Override - public void onContentAdded() { - channel.pipeline().get(ChunkedWriteHandler.class).resumeTransfer(); - } - }); - } - } - ChannelFuture writeFuture = channel.write(msg, channel.newProgressivePromise()); - - writeFuture.addListener(new ProgressListener(config, future.getAsyncHandler(), future, false, getContentLength()) { - public void operationComplete(ChannelProgressiveFuture cf) { - closeSilently(body); - super.operationComplete(cf); - } - }); - channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyByteArrayBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyByteArrayBody.java deleted file mode 100755 index 953461fb84..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyByteArrayBody.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request.body; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; - -import io.netty.channel.Channel; - -import java.io.IOException; - -public class NettyByteArrayBody implements NettyBody { - - private final byte[] bytes; - private final String contentType; - - public NettyByteArrayBody(byte[] bytes) { - this(bytes, null); - } - - public NettyByteArrayBody(byte[] bytes, String contentType) { - this.bytes = bytes; - this.contentType = contentType; - } - - public byte[] getBytes() { - return bytes; - } - - @Override - public long getContentLength() { - return bytes.length; - } - - @Override - public String getContentType() { - return contentType; - } - - @Override - public void write(Channel channel, NettyResponseFuture future, AsyncHttpClientConfig config) throws IOException { - throw new UnsupportedOperationException("This kind of body is supposed to be writen directly"); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java deleted file mode 100755 index 44a5349a7e..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request.body; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelProgressiveFuture; -import io.netty.channel.DefaultFileRegion; -import io.netty.channel.FileRegion; -import io.netty.handler.codec.http.LastHttpContent; -import io.netty.handler.stream.ChunkedFile; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.providers.netty.channel.ChannelManager; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.request.ProgressListener; - -public class NettyFileBody implements NettyBody { - - private final File file; - private final long offset; - private final long length; - private final NettyAsyncHttpProviderConfig nettyConfig; - - public NettyFileBody(File file, NettyAsyncHttpProviderConfig nettyConfig) throws IOException { - this(file, 0, file.length(), nettyConfig); - } - - public NettyFileBody(File file, long offset, long length, NettyAsyncHttpProviderConfig nettyConfig) throws IOException { - if (!file.isFile()) { - throw new IOException(String.format("File %s is not a file or doesn't exist", file.getAbsolutePath())); - } - this.file = file; - this.offset = offset; - this.length = length; - this.nettyConfig = nettyConfig; - } - - public File getFile() { - return file; - } - - public long getOffset() { - return offset; - } - - @Override - public long getContentLength() { - return length; - } - - @Override - public String getContentType() { - return null; - } - - @Override - public void write(Channel channel, NettyResponseFuture future, AsyncHttpClientConfig config) throws IOException { - final RandomAccessFile raf = new RandomAccessFile(file, "r"); - - try { - ChannelFuture writeFuture; - if (ChannelManager.isSslHandlerConfigured(channel.pipeline()) || nettyConfig.isDisableZeroCopy()) { - writeFuture = channel.write(new ChunkedFile(raf, offset, length, nettyConfig.getChunkedFileChunkSize()), channel.newProgressivePromise()); - } else { - FileRegion region = new DefaultFileRegion(raf.getChannel(), offset, length); - writeFuture = channel.write(region, channel.newProgressivePromise()); - } - writeFuture.addListener(new ProgressListener(config, future.getAsyncHandler(), future, false, getContentLength()) { - public void operationComplete(ChannelProgressiveFuture cf) { - closeSilently(raf); - super.operationComplete(cf); - } - }); - channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); - } catch (IOException ex) { - closeSilently(raf); - throw ex; - } - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyInputStreamBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyInputStreamBody.java deleted file mode 100755 index 9f7d841996..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyInputStreamBody.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request.body; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.request.ProgressListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelProgressiveFuture; -import io.netty.handler.codec.http.LastHttpContent; -import io.netty.handler.stream.ChunkedStream; - -import java.io.IOException; -import java.io.InputStream; - -public class NettyInputStreamBody implements NettyBody { - - private static final Logger LOGGER = LoggerFactory.getLogger(NettyInputStreamBody.class); - - private final InputStream inputStream; - - public NettyInputStreamBody(InputStream inputStream) { - this.inputStream = inputStream; - } - - public InputStream getInputStream() { - return inputStream; - } - - @Override - public long getContentLength() { - return -1L; - } - - @Override - public String getContentType() { - return null; - } - - @Override - public void write(Channel channel, NettyResponseFuture future, AsyncHttpClientConfig config) throws IOException { - final InputStream is = inputStream; - - if (future.isStreamWasAlreadyConsumed()) { - if (is.markSupported()) - is.reset(); - else { - LOGGER.warn("Stream has already been consumed and cannot be reset"); - return; - } - } else { - future.setStreamWasAlreadyConsumed(true); - } - - channel.write(new ChunkedStream(is), channel.newProgressivePromise()).addListener( - new ProgressListener(config, future.getAsyncHandler(), future, false, getContentLength()) { - public void operationComplete(ChannelProgressiveFuture cf) { - closeSilently(is); - super.operationComplete(cf); - } - }); - channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyMultipartBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyMultipartBody.java deleted file mode 100755 index 0c69440533..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyMultipartBody.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request.body; - -import static org.asynchttpclient.multipart.MultipartUtils.newMultipartBody; - -import java.util.List; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.multipart.MultipartBody; -import org.asynchttpclient.multipart.Part; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; - -public class NettyMultipartBody extends NettyBodyBody { - - private final String contentType; - - public NettyMultipartBody(List parts, FluentCaseInsensitiveStringsMap headers, NettyAsyncHttpProviderConfig nettyConfig) { - this(newMultipartBody(parts, headers), nettyConfig); - } - - private NettyMultipartBody(MultipartBody body, NettyAsyncHttpProviderConfig nettyConfig) { - super(body, nettyConfig); - contentType = body.getContentType(); - } - - @Override - public String getContentType() { - return contentType; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java deleted file mode 100755 index 2c56a04046..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request.timeout; - -import static org.asynchttpclient.util.DateUtils.millisTime; -import io.netty.util.Timeout; - -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.request.NettyRequestSender; - -public class ReadTimeoutTimerTask extends TimeoutTimerTask { - - private final long readTimeout; - private final long requestTimeoutInstant; - - public ReadTimeoutTimerTask(// - NettyResponseFuture nettyResponseFuture,// - NettyRequestSender requestSender,// - TimeoutsHolder timeoutsHolder,// - long requestTimeout,// - long readTimeout) { - super(nettyResponseFuture, requestSender, timeoutsHolder); - this.readTimeout = readTimeout; - requestTimeoutInstant = requestTimeout >= 0 ? nettyResponseFuture.getStart() + requestTimeout : Long.MAX_VALUE; - } - - public void run(Timeout timeout) throws Exception { - - if (done.getAndSet(true) || requestSender.isClosed()) - return; - - if (nettyResponseFuture.isDone()) { - timeoutsHolder.cancel(); - return; - } - - long now = millisTime(); - - long currentReadTimeoutInstant = readTimeout + nettyResponseFuture.getLastTouch(); - long durationBeforeCurrentReadTimeout = currentReadTimeoutInstant - now; - - if (durationBeforeCurrentReadTimeout <= 0L) { - // idleConnectionTimeout reached - String message = "Read timeout to " + remoteAddress + " of " + readTimeout + " ms"; - long durationSinceLastTouch = now - nettyResponseFuture.getLastTouch(); - expire(message, durationSinceLastTouch); - // cancel request timeout sibling - timeoutsHolder.cancel(); - - } else if (currentReadTimeoutInstant < requestTimeoutInstant) { - // reschedule - done.set(false); - timeoutsHolder.readTimeout = requestSender.newTimeout(this, durationBeforeCurrentReadTimeout); - - } else { - // otherwise, no need to reschedule: requestTimeout will happen sooner - timeoutsHolder.readTimeout = null; - } - - // this task should be evacuated from the timer but who knows - nettyResponseFuture = null; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java deleted file mode 100755 index 2c448a4b80..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request.timeout; - -import static org.asynchttpclient.util.DateUtils.millisTime; -import io.netty.util.Timeout; - -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.request.NettyRequestSender; - -public class RequestTimeoutTimerTask extends TimeoutTimerTask { - - private final long requestTimeout; - - public RequestTimeoutTimerTask(// - NettyResponseFuture nettyResponseFuture,// - NettyRequestSender requestSender,// - TimeoutsHolder timeoutsHolder,// - long requestTimeout) { - super(nettyResponseFuture, requestSender, timeoutsHolder); - this.requestTimeout = requestTimeout; - } - - public void run(Timeout timeout) throws Exception { - - if (done.getAndSet(true) || requestSender.isClosed()) - return; - - // in any case, cancel possible idleConnectionTimeout sibling - timeoutsHolder.cancel(); - - if (nettyResponseFuture.isDone()) - return; - - String message = "Request timed out to " + remoteAddress + " of " + requestTimeout + " ms"; - long age = millisTime() - nettyResponseFuture.getStart(); - expire(message, age); - - // this task should be evacuated from the timer but who knows - nettyResponseFuture = null; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutTimerTask.java deleted file mode 100755 index 872ce442d9..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutTimerTask.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request.timeout; - -import io.netty.util.TimerTask; - -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.request.NettyRequestSender; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public abstract class TimeoutTimerTask implements TimerTask { - - private static final Logger LOGGER = LoggerFactory.getLogger(TimeoutTimerTask.class); - - protected final AtomicBoolean done = new AtomicBoolean(); - protected volatile NettyResponseFuture nettyResponseFuture; - protected final NettyRequestSender requestSender; - protected final TimeoutsHolder timeoutsHolder; - protected final String remoteAddress; - - public TimeoutTimerTask(NettyResponseFuture nettyResponseFuture, NettyRequestSender requestSender, TimeoutsHolder timeoutsHolder) { - this.nettyResponseFuture = nettyResponseFuture; - this.requestSender = requestSender; - this.timeoutsHolder = timeoutsHolder; - // saving remote address as the channel might be removed from the future when an exception occurs - remoteAddress = nettyResponseFuture.getChannelRemoteAddress().toString(); - } - - protected void expire(String message, long time) { - LOGGER.debug("{} for {} after {} ms", message, nettyResponseFuture, time); - requestSender.abort(nettyResponseFuture.channel(), nettyResponseFuture, new TimeoutException(message)); - } - - /** - * When the timeout is cancelled, it could still be referenced for quite some time in the Timer. - * Holding a reference to the future might mean holding a reference to the channel, and heavy objects such as SslEngines - */ - public void clean() { - if (done.compareAndSet(false, true)) - nettyResponseFuture = null; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java deleted file mode 100755 index 6fb7fa3281..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.request.timeout; - -import io.netty.util.Timeout; - -import java.util.concurrent.atomic.AtomicBoolean; - -public class TimeoutsHolder { - - private final AtomicBoolean cancelled = new AtomicBoolean(); - public volatile Timeout requestTimeout; - public volatile Timeout readTimeout; - - public void cancel() { - if (cancelled.compareAndSet(false, true)) { - if (requestTimeout != null) { - requestTimeout.cancel(); - RequestTimeoutTimerTask.class.cast(requestTimeout.task()).clean(); - requestTimeout = null; - } - if (readTimeout != null) { - readTimeout.cancel(); - ReadTimeoutTimerTask.class.cast(readTimeout.task()).clean(); - readTimeout = null; - } - } - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerNettyResponseBodyPart.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerNettyResponseBodyPart.java deleted file mode 100755 index e993bd4975..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerNettyResponseBodyPart.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.response; - -import static org.asynchttpclient.providers.netty.util.ByteBufUtils.*; - -import io.netty.buffer.ByteBuf; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * A callback class used when an HTTP response body is received. - * Bytes are eagerly fetched from the ByteBuf - */ -public class EagerNettyResponseBodyPart extends NettyResponseBodyPart { - - private final byte[] bytes; - - public EagerNettyResponseBodyPart(ByteBuf buf, boolean last) { - super(last); - bytes = byteBuf2Bytes(buf); - } - - /** - * Return the response body's part bytes received. - * - * @return the response body's part bytes received. - */ - @Override - public byte[] getBodyPartBytes() { - return bytes; - } - - @Override - public InputStream readBodyPartBytes() { - return new ByteArrayInputStream(bytes); - } - - @Override - public int length() { - return bytes.length; - } - - @Override - public int writeTo(OutputStream outputStream) throws IOException { - outputStream.write(bytes); - return length(); - } - - @Override - public ByteBuffer getBodyByteBuffer() { - return ByteBuffer.wrap(bytes); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/LazyNettyResponseBodyPart.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/LazyNettyResponseBodyPart.java deleted file mode 100755 index d3f136b9f1..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/LazyNettyResponseBodyPart.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.response; - -import io.netty.buffer.ByteBuf; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * A callback class used when an HTTP response body is received. - */ -public class LazyNettyResponseBodyPart extends NettyResponseBodyPart { - - private static final String ERROR_MESSAGE = "This implementation is intended for one to directly read from the underlying ByteBuf and release after usage. Not for the fainted heart!"; - - private final ByteBuf buf; - - public LazyNettyResponseBodyPart(ByteBuf buf, boolean last) { - super(last); - this.buf = buf; - } - - public ByteBuf getBuf() { - return buf; - } - - /** - * Return the response body's part bytes received. - * - * @return the response body's part bytes received. - */ - @Override - public byte[] getBodyPartBytes() { - throw new UnsupportedOperationException(ERROR_MESSAGE); - } - - @Override - public InputStream readBodyPartBytes() { - throw new UnsupportedOperationException(ERROR_MESSAGE); - } - - @Override - public int length() { - throw new UnsupportedOperationException(ERROR_MESSAGE); - } - - @Override - public int writeTo(OutputStream outputStream) throws IOException { - throw new UnsupportedOperationException(ERROR_MESSAGE); - } - - @Override - public ByteBuffer getBodyByteBuffer() { - throw new UnsupportedOperationException(ERROR_MESSAGE); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponse.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponse.java deleted file mode 100755 index 17d593541e..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponse.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.response; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.contentToBytes; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; -import io.netty.handler.codec.http.HttpHeaders; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.cookie.CookieDecoder; -import org.asynchttpclient.date.TimeConverter; -import org.asynchttpclient.providers.ResponseBase; - -/** - * Wrapper around the {@link org.asynchttpclient.Response} API. - */ -public class NettyResponse extends ResponseBase { - - private final TimeConverter timeConverter; - - public NettyResponse(HttpResponseStatus status,// - HttpResponseHeaders headers,// - List bodyParts,// - TimeConverter timeConverter) { - super(status, headers, bodyParts); - this.timeConverter = timeConverter; - } - - @Override - public String getResponseBodyExcerpt(int maxLength) throws IOException { - return getResponseBodyExcerpt(maxLength, null); - } - - public String getResponseBodyExcerpt(int maxLength, String charset) throws IOException { - // should be fine; except that it may split multi-byte chars (last char may become '?') - charset = calculateCharset(charset); - byte[] b = contentToBytes(bodyParts, maxLength); - return new String(b, charset); - } - - protected List buildCookies() { - - List setCookieHeaders = headers.getHeaders().get(HttpHeaders.Names.SET_COOKIE2); - - if (!isNonEmpty(setCookieHeaders)) { - setCookieHeaders = headers.getHeaders().get(HttpHeaders.Names.SET_COOKIE); - } - - if (isNonEmpty(setCookieHeaders)) { - List cookies = new ArrayList(); - for (String value : setCookieHeaders) { - Cookie c = CookieDecoder.decode(value, timeConverter); - if (c != null) - cookies.add(c); - } - return Collections.unmodifiableList(cookies); - } - - return Collections.emptyList(); - } - - @Override - public byte[] getResponseBodyAsBytes() throws IOException { - return getResponseBodyAsByteBuffer().array(); - } - - @Override - public ByteBuffer getResponseBodyAsByteBuffer() throws IOException { - - int length = 0; - for (HttpResponseBodyPart part : bodyParts) - length += part.length(); - - ByteBuffer target = ByteBuffer.wrap(new byte[length]); - for (HttpResponseBodyPart part : bodyParts) - target.put(part.getBodyPartBytes()); - - return target; - } - - @Override - public String getResponseBody() throws IOException { - return getResponseBody(null); - } - - @Override - public String getResponseBody(String charset) throws IOException { - return new String(getResponseBodyAsBytes(), calculateCharset(charset)); - } - - @Override - public InputStream getResponseBodyAsStream() throws IOException { - return new ByteArrayInputStream(getResponseBodyAsBytes()); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseBodyPart.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseBodyPart.java deleted file mode 100755 index 76399a14e6..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseBodyPart.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.response; - -import org.asynchttpclient.HttpResponseBodyPart; - -/** - * A callback class used when an HTTP response body is received. - */ -public abstract class NettyResponseBodyPart extends HttpResponseBodyPart { - - private final boolean last; - private boolean closeConnection; - - public NettyResponseBodyPart(boolean last) { - this.last = last; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isLast() { - return last; - } - - /** - * {@inheritDoc} - */ - @Override - public void markUnderlyingConnectionAsToBeClosed() { - closeConnection = true; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isUnderlyingConnectionToBeClosed() { - return closeConnection; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseHeaders.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseHeaders.java deleted file mode 100755 index 375f0cd3bf..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseHeaders.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.response; - -import io.netty.handler.codec.http.HttpHeaders; - -import java.util.Map; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseHeaders; - -/** - * A class that represent the HTTP headers. - */ -public class NettyResponseHeaders extends HttpResponseHeaders { - - private final HttpHeaders responseHeaders; - private final HttpHeaders trailingHeaders; - private final FluentCaseInsensitiveStringsMap headers; - - // FIXME unused AsyncHttpProvider provider - public NettyResponseHeaders(HttpHeaders responseHeaders) { - this(responseHeaders, null); - } - - public NettyResponseHeaders(HttpHeaders responseHeaders, HttpHeaders traillingHeaders) { - super(traillingHeaders != null); - this.responseHeaders = responseHeaders; - this.trailingHeaders = traillingHeaders; - headers = computerHeaders(); - } - - private FluentCaseInsensitiveStringsMap computerHeaders() { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - for (Map.Entry header : responseHeaders) { - h.add(header.getKey(), header.getValue()); - } - - if (trailingHeaders != null) { - for (Map.Entry header : trailingHeaders) { - h.add(header.getKey(), header.getValue()); - } - } - - return h; - } - - /** - * Return the HTTP header - * - * @return an {@link org.asynchttpclient.FluentCaseInsensitiveStringsMap} - */ - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return headers; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseStatus.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseStatus.java deleted file mode 100755 index f869f35d1a..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseStatus.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.response; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; -import org.asynchttpclient.uri.UriComponents; - -import io.netty.handler.codec.http.HttpResponse; - -import java.util.List; - -/** - * A class that represent the HTTP response' status line (code + text) - */ -public class NettyResponseStatus extends HttpResponseStatus { - - private final HttpResponse response; - - public NettyResponseStatus(UriComponents uri, AsyncHttpClientConfig config, HttpResponse response) { - super(uri, config); - this.response = response; - } - - @Override - public Response prepareResponse(HttpResponseHeaders headers, List bodyParts) { - return new NettyResponse(this, headers, bodyParts, config.getTimeConverter()); - } - - /** - * Return the response status code - * - * @return the response status code - */ - public int getStatusCode() { - return response.getStatus().code(); - } - - /** - * Return the response status text - * - * @return the response status text - */ - public String getStatusText() { - return response.getStatus().reasonPhrase(); - } - - @Override - public String getProtocolName() { - return response.getProtocolVersion().protocolName(); - } - - @Override - public int getProtocolMajorVersion() { - return response.getProtocolVersion().majorVersion(); - } - - @Override - public int getProtocolMinorVersion() { - return response.getProtocolVersion().minorVersion(); - } - - @Override - public String getProtocolText() { - return response.getProtocolVersion().text(); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtils.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtils.java deleted file mode 100755 index f370ac153b..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.providers.netty.util; - -import io.netty.buffer.ByteBuf; - -import java.util.List; - -public final class ByteBufUtils { - - public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - - private ByteBufUtils() { - } - - public static byte[] byteBuf2Bytes(ByteBuf buf) { - int readable = buf.readableBytes(); - int readerIndex = buf.readerIndex(); - if (buf.hasArray()) { - byte[] array = buf.array(); - if (buf.arrayOffset() == 0 && readerIndex == 0 && array.length == readable) { - return array; - } - } - byte[] array = new byte[readable]; - buf.getBytes(readerIndex, array); - return array; - } - - public static byte[] byteBufs2Bytes(List bufs) { - - if (bufs.isEmpty()) { - return EMPTY_BYTE_ARRAY; - - } else if (bufs.size() == 1) { - return byteBuf2Bytes(bufs.get(0)); - - } else { - int totalSize = 0; - for (ByteBuf buf : bufs) { - totalSize += buf.readableBytes(); - } - - byte[] bytes = new byte[totalSize]; - int offset = 0; - for (ByteBuf buf : bufs) { - int readable = buf.readableBytes(); - buf.getBytes(buf.readerIndex(), bytes, offset, readable); - offset += readable; - } - return bytes; - } - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java deleted file mode 100755 index baac5bddbd..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.util; - -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import java.util.List; - -import org.asynchttpclient.uri.UriComponents; - -public final class HttpUtils { - - public static final String HTTPS = "https"; - public static final String HTTP = "http"; - public static final String WEBSOCKET = "ws"; - public static final String WEBSOCKET_SSL = "wss"; - - private HttpUtils() { - } - - public static boolean isNTLM(List auth) { - return isNonEmpty(auth) && auth.get(0).startsWith("NTLM"); - } - - public static boolean isWebSocket(String scheme) { - return WEBSOCKET.equals(scheme) || WEBSOCKET_SSL.equals(scheme); - } - - public static boolean isSecure(String scheme) { - return HTTPS.equals(scheme) || WEBSOCKET_SSL.equals(scheme); - } - - public static boolean isSecure(UriComponents uri) { - return isSecure(uri.getScheme()); - } - - public static boolean useProxyConnect(UriComponents uri) { - return isSecure(uri) || isWebSocket(uri.getScheme()); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java deleted file mode 100755 index 3cdb818a50..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.ws; - -import static io.netty.buffer.Unpooled.wrappedBuffer; -import static org.asynchttpclient.util.StandardCharsets.UTF_8; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFutureListener; -import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; -import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; -import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; -import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; -import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.ConcurrentLinkedQueue; - -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; -import org.asynchttpclient.websocket.WebSocket; -import org.asynchttpclient.websocket.WebSocketByteFragmentListener; -import org.asynchttpclient.websocket.WebSocketByteListener; -import org.asynchttpclient.websocket.WebSocketCloseCodeReasonListener; -import org.asynchttpclient.websocket.WebSocketListener; -import org.asynchttpclient.websocket.WebSocketPingListener; -import org.asynchttpclient.websocket.WebSocketPongListener; -import org.asynchttpclient.websocket.WebSocketTextFragmentListener; -import org.asynchttpclient.websocket.WebSocketTextListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class NettyWebSocket implements WebSocket { - - private static final Logger LOGGER = LoggerFactory.getLogger(NettyWebSocket.class); - - protected final Channel channel; - protected final Collection listeners; - protected final int maxBufferSize; - private int bufferSize; - private List _fragments; - private volatile boolean interestedInByteMessages; - private volatile boolean interestedInTextMessages; - - public NettyWebSocket(Channel channel, NettyAsyncHttpProviderConfig nettyConfig) { - this(channel, nettyConfig, new ConcurrentLinkedQueue()); - } - - public NettyWebSocket(Channel channel, NettyAsyncHttpProviderConfig nettyConfig, Collection listeners) { - this.channel = channel; - this.listeners = listeners; - maxBufferSize = nettyConfig.getWebSocketMaxBufferSize(); - } - - @Override - public WebSocket sendMessage(byte[] message) { - channel.writeAndFlush(new BinaryWebSocketFrame(wrappedBuffer(message))); - return this; - } - - @Override - public WebSocket stream(byte[] fragment, boolean last) { - channel.writeAndFlush(new BinaryWebSocketFrame(last, 0, wrappedBuffer(fragment))); - return this; - } - - @Override - public WebSocket stream(byte[] fragment, int offset, int len, boolean last) { - channel.writeAndFlush(new BinaryWebSocketFrame(last, 0, wrappedBuffer(fragment, offset, len))); - return this; - } - - @Override - public WebSocket sendTextMessage(String message) { - channel.writeAndFlush(new TextWebSocketFrame(message)); - return this; - } - - @Override - public WebSocket streamText(String fragment, boolean last) { - channel.writeAndFlush(new TextWebSocketFrame(last, 0, fragment)); - return this; - } - - @Override - public WebSocket sendPing(byte[] payload) { - channel.writeAndFlush(new PingWebSocketFrame(wrappedBuffer(payload))); - return this; - } - - @Override - public WebSocket sendPong(byte[] payload) { - channel.writeAndFlush(new PongWebSocketFrame(wrappedBuffer(payload))); - return this; - } - - @Override - public boolean isOpen() { - return channel.isOpen(); - } - - @Override - public void close() { - if (channel.isOpen()) { - onClose(); - listeners.clear(); - channel.writeAndFlush(new CloseWebSocketFrame()).addListener(ChannelFutureListener.CLOSE); - } - } - - public void close(int statusCode, String reason) { - onClose(statusCode, reason); - listeners.clear(); - } - - public void onError(Throwable t) { - for (WebSocketListener listener : listeners) { - try { - listener.onError(t); - } catch (Throwable t2) { - LOGGER.error("", t2); - } - } - } - - protected void onClose() { - onClose(1000, "Normal closure; the connection successfully completed whatever purpose for which it was created."); - } - - public void onClose(int code, String reason) { - for (WebSocketListener l : listeners) { - try { - if (l instanceof WebSocketCloseCodeReasonListener) { - WebSocketCloseCodeReasonListener.class.cast(l).onClose(this, code, reason); - } - l.onClose(this); - } catch (Throwable t) { - l.onError(t); - } - } - } - - @Override - public String toString() { - return "NettyWebSocket{channel=" + channel + '}'; - } - - private boolean hasWebSocketByteListener() { - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketByteListener) - return true; - } - return false; - } - - private boolean hasWebSocketTextListener() { - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketTextListener) - return true; - } - return false; - } - - @Override - public WebSocket addWebSocketListener(WebSocketListener l) { - listeners.add(l); - interestedInByteMessages = interestedInByteMessages || l instanceof WebSocketByteListener; - interestedInTextMessages = interestedInTextMessages || l instanceof WebSocketTextListener; - return this; - } - - @Override - public WebSocket removeWebSocketListener(WebSocketListener l) { - listeners.remove(l); - - if (l instanceof WebSocketByteListener) - interestedInByteMessages = hasWebSocketByteListener(); - if (l instanceof WebSocketTextListener) - interestedInTextMessages = hasWebSocketTextListener(); - - return this; - } - - private List fragments() { - if (_fragments == null) - _fragments = new ArrayList(2); - return _fragments; - } - - private void bufferFragment(byte[] buffer) { - bufferSize += buffer.length; - if (bufferSize > maxBufferSize) { - onError(new Exception("Exceeded Netty Web Socket maximum buffer size of " + maxBufferSize)); - reset(); - close(); - } else { - fragments().add(buffer); - } - } - - private void reset() { - fragments().clear(); - bufferSize = 0; - } - - private void notifyByteListeners(byte[] message) { - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketByteListener) - WebSocketByteListener.class.cast(listener).onMessage(message); - } - } - - private void notifyTextListeners(byte[] bytes) { - String message = new String(bytes, UTF_8); - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketTextListener) - WebSocketTextListener.class.cast(listener).onMessage(message); - } - } - - public void onBinaryFragment(HttpResponseBodyPart part) { - - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketByteFragmentListener) - WebSocketByteFragmentListener.class.cast(listener).onFragment(part); - } - - if (interestedInByteMessages) { - byte[] fragment = NettyResponseBodyPart.class.cast(part).getBodyPartBytes(); - - if (part.isLast()) { - if (bufferSize == 0) { - notifyByteListeners(fragment); - - } else { - bufferFragment(fragment); - notifyByteListeners(fragmentsBytes()); - } - - reset(); - - } else - bufferFragment(fragment); - } - } - - private byte[] fragmentsBytes() { - ByteArrayOutputStream os = new ByteArrayOutputStream(bufferSize); - for (byte[] bytes : _fragments) - try { - os.write(bytes); - } catch (IOException e) { - // yeah, right - } - return os.toByteArray(); - } - - public void onTextFragment(HttpResponseBodyPart part) { - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketTextFragmentListener) - WebSocketTextFragmentListener.class.cast(listener).onFragment(part); - } - - if (interestedInTextMessages) { - byte[] fragment = NettyResponseBodyPart.class.cast(part).getBodyPartBytes(); - - if (part.isLast()) { - if (bufferSize == 0) { - notifyTextListeners(fragment); - - } else { - bufferFragment(fragment); - notifyTextListeners(fragmentsBytes()); - } - - reset(); - - } else - bufferFragment(fragment); - } - } - - public void onPing(HttpResponseBodyPart part) { - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketPingListener) - // bytes are cached in the part - WebSocketPingListener.class.cast(listener).onPing(part.getBodyPartBytes()); - } - } - - public void onPong(HttpResponseBodyPart part) { - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketPongListener) - // bytes are cached in the part - WebSocketPongListener.class.cast(listener).onPong(part.getBodyPartBytes()); - } - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/WebSocketUtils.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/WebSocketUtils.java deleted file mode 100755 index c308edaea4..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/WebSocketUtils.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.ws; - -import org.asynchttpclient.util.Base64; -import org.asynchttpclient.util.StandardCharsets; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public final class WebSocketUtils { - - public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - - public static String getKey() { - byte[] nonce = createRandomBytes(16); - return base64Encode(nonce); - } - - public static String getAcceptKey(String key) throws UnsupportedEncodingException { - String acceptSeed = key + MAGIC_GUID; - byte[] sha1 = sha1(acceptSeed.getBytes(StandardCharsets.US_ASCII)); - return base64Encode(sha1); - } - - public static byte[] md5(byte[] bytes) { - try { - MessageDigest md = MessageDigest.getInstance("MD5"); - return md.digest(bytes); - } catch (NoSuchAlgorithmException e) { - throw new InternalError("MD5 not supported on this platform"); - } - } - - public static byte[] sha1(byte[] bytes) { - try { - MessageDigest md = MessageDigest.getInstance("SHA1"); - return md.digest(bytes); - } catch (NoSuchAlgorithmException e) { - throw new InternalError("SHA-1 not supported on this platform"); - } - } - - public static String base64Encode(byte[] bytes) { - return Base64.encode(bytes); - } - - public static byte[] createRandomBytes(int size) { - byte[] bytes = new byte[size]; - - for (int i = 0; i < size; i++) { - bytes[i] = (byte) createRandomNumber(0, 255); - } - - return bytes; - } - - public static int createRandomNumber(int min, int max) { - return (int) (Math.random() * max + min); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderTest.java deleted file mode 100644 index 4ceea15708..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.AbstractBasicTest; - -public class NettyAsyncHttpProviderTest extends AbstractBasicTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderBasicTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderBasicTest.java deleted file mode 100644 index 79c5112869..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderBasicTest.java +++ /dev/null @@ -1,38 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.async.AsyncProvidersBasicTest; - -import io.netty.channel.ChannelOption; - -public class NettyAsyncProviderBasicTest extends AsyncProvidersBasicTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - @Override - protected AsyncHttpProviderConfig getProviderConfig() { - return new NettyAsyncHttpProviderConfig().addChannelOption(ChannelOption.TCP_NODELAY, Boolean.TRUE); - } - - @Override - protected String acceptEncodingHeader() { - return "gzip,deflate"; - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderPipelineTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderPipelineTest.java deleted file mode 100644 index 15e2a1f7bb..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderPipelineTest.java +++ /dev/null @@ -1,92 +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.providers.netty; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.async.AbstractBasicTest; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig.AdditionalChannelInitializer; -import org.testng.annotations.Test; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.handler.codec.http.HttpMessage; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -public class NettyAsyncProviderPipelineTest extends AbstractBasicTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - @Test(groups = { "standalone", "netty_provider" }) - public void asyncPipelineTest() throws Exception { - - NettyAsyncHttpProviderConfig nettyConfig = new NettyAsyncHttpProviderConfig(); - nettyConfig.setHttpAdditionalChannelInitializer(new AdditionalChannelInitializer() { - public void initChannel(Channel ch) throws Exception { - // super.initPlainChannel(ch); - ch.pipeline().addBefore("inflater", "copyEncodingHeader", new CopyEncodingHandler()); - } - }); - AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setCompressionEnabled(true) - .setAsyncHttpClientProviderConfig(nettyConfig).build()); - - try { - final CountDownLatch l = new CountDownLatch(1); - Request request = new RequestBuilder("GET").setUrl(getTargetUrl()).build(); - p.executeRequest(request, new AsyncCompletionHandlerAdapter() { - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("X-Original-Content-Encoding"), ""); - } finally { - l.countDown(); - } - return response; - } - }).get(); - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - p.close(); - } - } - - private static class CopyEncodingHandler extends ChannelInboundHandlerAdapter { - @Override - public void channelRead(ChannelHandlerContext ctx, Object e) { - if (e instanceof HttpMessage) { - HttpMessage m = (HttpMessage) e; - // for test there is no Content-Encoding header so just hard - // coding value - // for verification - m.headers().set("X-Original-Content-Encoding", ""); - } - ctx.fireChannelRead(e); - } - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncResponseTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncResponseTest.java deleted file mode 100644 index 9158571853..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncResponseTest.java +++ /dev/null @@ -1,93 +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.providers.netty; - -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.providers.netty.response.NettyResponse; -import org.asynchttpclient.providers.netty.response.NettyResponseStatus; -import org.testng.annotations.Test; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.TimeZone; - -/** - * @author Benjamin Hanzelmann - */ -public class NettyAsyncResponseTest { - - @Test(groups = "standalone") - public void testCookieParseExpires() { - // e.g. "Sun, 06-Feb-2012 03:45:24 GMT"; - SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z", Locale.US); - sdf.setTimeZone(TimeZone.getTimeZone("GMT")); - - Date date = new Date(System.currentTimeMillis() + 60000); - final String cookieDef = String.format("efmembercheck=true; expires=%s; path=/; domain=.eclipse.org", sdf.format(date)); - - NettyResponse response = new NettyResponse(new NettyResponseStatus(null, null, null), new HttpResponseHeaders() { - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); - } - }, null, null); - - List cookies = response.getCookies(); - assertEquals(cookies.size(), 1); - - Cookie cookie = cookies.get(0); - long originalDateWith1SecPrecision = date.getTime() / 1000 * 1000; - assertEquals(cookie.getExpires(), originalDateWith1SecPrecision); - } - - @Test(groups = "standalone") - public void testCookieParseMaxAge() { - final String cookieDef = "efmembercheck=true; max-age=60; path=/; domain=.eclipse.org"; - NettyResponse response = new NettyResponse(new NettyResponseStatus(null, null, null), new HttpResponseHeaders() { - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); - } - }, null, null); - List cookies = response.getCookies(); - assertEquals(cookies.size(), 1); - - Cookie cookie = cookies.get(0); - assertEquals(cookie.getMaxAge(), 60); - } - - @Test(groups = "standalone") - public void testCookieParseWeirdExpiresValue() { - final String cookieDef = "efmembercheck=true; expires=60; path=/; domain=.eclipse.org"; - NettyResponse response = new NettyResponse(new NettyResponseStatus(null, null, null), new HttpResponseHeaders() { - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); - } - }, null, null); - - List cookies = response.getCookies(); - assertEquals(cookies.size(), 1); - - Cookie cookie = cookies.get(0); - assertEquals(cookie.getMaxAge(), -1); - } - -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncStreamHandlerTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncStreamHandlerTest.java deleted file mode 100644 index 1c1bea8935..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncStreamHandlerTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.AsyncStreamHandlerTest; - -public class NettyAsyncStreamHandlerTest extends AsyncStreamHandlerTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncStreamLifecycleTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncStreamLifecycleTest.java deleted file mode 100644 index 9760aa4296..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncStreamLifecycleTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.AsyncStreamLifecycleTest; - -public class NettyAsyncStreamLifecycleTest extends AsyncStreamLifecycleTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAuthTimeoutTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAuthTimeoutTest.java deleted file mode 100644 index 1056d502fa..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAuthTimeoutTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.AuthTimeoutTest; - -public class NettyAuthTimeoutTest extends AuthTimeoutTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBasicAuthTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBasicAuthTest.java deleted file mode 100644 index 589cfd1e6f..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBasicAuthTest.java +++ /dev/null @@ -1,30 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.BasicAuthTest; - -public class NettyBasicAuthTest extends BasicAuthTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - @Override - public String getProviderClass() { - return NettyAsyncHttpProvider.class.getName(); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBasicHttpsTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBasicHttpsTest.java deleted file mode 100644 index 5790fd96b1..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBasicHttpsTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.BasicHttpsTest; - -public class NettyBasicHttpsTest extends BasicHttpsTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBodyChunkTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBodyChunkTest.java deleted file mode 100644 index e8dacd9680..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBodyChunkTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.BodyChunkTest; - -public class NettyBodyChunkTest extends BodyChunkTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBodyDeferringAsyncHandlerTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBodyDeferringAsyncHandlerTest.java deleted file mode 100644 index 01e8daba42..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBodyDeferringAsyncHandlerTest.java +++ /dev/null @@ -1,26 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.BodyDeferringAsyncHandlerTest; - -public class NettyBodyDeferringAsyncHandlerTest extends BodyDeferringAsyncHandlerTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyByteBufferCapacityTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyByteBufferCapacityTest.java deleted file mode 100644 index 8cabb0c637..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyByteBufferCapacityTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ByteBufferCapacityTest; - -public class NettyByteBufferCapacityTest extends ByteBufferCapacityTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyChunkingTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyChunkingTest.java deleted file mode 100644 index 7783e21d58..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyChunkingTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.asynchttpclient.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ChunkingTest; - -public class NettyChunkingTest extends ChunkingTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyComplexClientTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyComplexClientTest.java deleted file mode 100644 index 7cb83020d0..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyComplexClientTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ComplexClientTest; - -public class NettyComplexClientTest extends ComplexClientTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyConnectionPoolTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyConnectionPoolTest.java deleted file mode 100644 index ea3f0d573c..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyConnectionPoolTest.java +++ /dev/null @@ -1,152 +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.providers.netty; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.asynchttpclient.async.ConnectionPoolTest; -import org.asynchttpclient.providers.netty.channel.pool.ChannelPool; -import org.testng.annotations.Test; - -import io.netty.channel.Channel; - -import java.net.ConnectException; -import java.util.concurrent.TimeUnit; - -public class NettyConnectionPoolTest extends ConnectionPoolTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - @Test(groups = { "standalone", "default_provider" }) - public void testInvalidConnectionsPool() { - ChannelPool cp = new ChannelPool() { - - public boolean offer(Channel channel, String poolKey) { - return false; - } - - public Channel poll(String poolKey) { - return null; - } - - public boolean removeAll(Channel channel) { - return false; - } - - public boolean isOpen() { - return false; - } - - public void destroy() { - - } - }; - - NettyAsyncHttpProviderConfig providerConfig = new NettyAsyncHttpProviderConfig(); - providerConfig.setChannelPool(cp); - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAsyncHttpClientProviderConfig(providerConfig) - .build()); - try { - Exception exception = null; - try { - client.prepareGet(getTargetUrl()).execute().get(TIMEOUT, TimeUnit.SECONDS); - } catch (Exception ex) { - ex.printStackTrace(); - exception = ex; - } - assertNotNull(exception); - assertEquals(exception.getMessage(), "Too many connections -1"); - } finally { - client.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testValidConnectionsPool() { - ChannelPool cp = new ChannelPool() { - - public boolean offer(Channel channel, String poolKey) { - return true; - } - - public Channel poll(String poolKey) { - return null; - } - - public boolean removeAll(Channel channel) { - return false; - } - - public boolean isOpen() { - return true; - } - - public void destroy() { - - } - }; - - NettyAsyncHttpProviderConfig providerConfig = new NettyAsyncHttpProviderConfig(); - providerConfig.setChannelPool(cp); - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAsyncHttpClientProviderConfig(providerConfig) - .build()); - try { - Exception exception = null; - try { - client.prepareGet(getTargetUrl()).execute().get(TIMEOUT, TimeUnit.SECONDS); - } catch (Exception ex) { - ex.printStackTrace(); - exception = ex; - } - assertNull(exception); - } finally { - client.close(); - } - } - - @Test - public void testHostNotContactable() { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().build()); - - try { - String url = null; - try { - url = "http://127.0.0.1/" + findFreePort(); - } catch (Exception e) { - fail("unable to find free port to simulate downed host"); - } - int i; - for (i = 0; i < 2; i++) { - try { - log.info("{} requesting url [{}]...", i, url); - Response response = client.prepareGet(url).execute().get(); - log.info("{} response [{}].", i, response); - fail("Shouldn't be here: should get an exception instead"); - } catch (Exception ex) { - assertNotNull(ex.getCause()); - Throwable cause = ex.getCause(); - assertTrue(cause instanceof ConnectException); - } - } - } finally { - client.close(); - } - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyDigestAuthTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyDigestAuthTest.java deleted file mode 100644 index 205fb73a1b..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyDigestAuthTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.DigestAuthTest; - -public class NettyDigestAuthTest extends DigestAuthTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyEmptyBodyTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyEmptyBodyTest.java deleted file mode 100644 index 8cdbe19e68..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyEmptyBodyTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.EmptyBodyTest; - -public class NettyEmptyBodyTest extends EmptyBodyTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyErrorResponseTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyErrorResponseTest.java deleted file mode 100644 index ee34de01fd..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyErrorResponseTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ErrorResponseTest; - -public class NettyErrorResponseTest extends ErrorResponseTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyExpect100ContinueTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyExpect100ContinueTest.java deleted file mode 100644 index ce9759922e..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyExpect100ContinueTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.Expect100ContinueTest; - -public class NettyExpect100ContinueTest extends Expect100ContinueTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFastUnauthorizedUploadTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFastUnauthorizedUploadTest.java deleted file mode 100644 index 51128081d6..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFastUnauthorizedUploadTest.java +++ /dev/null @@ -1,26 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.FastUnauthorizedUploadTest; -import org.testng.annotations.Test; - -@Test -public class NettyFastUnauthorizedUploadTest extends FastUnauthorizedUploadTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFilePartLargeFileTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFilePartLargeFileTest.java deleted file mode 100644 index 8b9adcbd6b..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFilePartLargeFileTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.FilePartLargeFileTest; - -public class NettyFilePartLargeFileTest extends FilePartLargeFileTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFilterTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFilterTest.java deleted file mode 100644 index f725ea2288..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFilterTest.java +++ /dev/null @@ -1,26 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.FilterTest; -import org.testng.annotations.Test; - -@Test -public class NettyFilterTest extends FilterTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFollowingThreadTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFollowingThreadTest.java deleted file mode 100644 index c3e2c8b757..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFollowingThreadTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.FollowingThreadTest; - -public class NettyFollowingThreadTest extends FollowingThreadTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyHead302Test.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyHead302Test.java deleted file mode 100644 index 8fc323e3b8..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyHead302Test.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.Head302Test; - -public class NettyHead302Test extends Head302Test { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyHostnameVerifierTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyHostnameVerifierTest.java deleted file mode 100644 index 004a2eb43e..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyHostnameVerifierTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.HostnameVerifierTest; - -public class NettyHostnameVerifierTest extends HostnameVerifierTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyHttpToHttpsRedirectTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyHttpToHttpsRedirectTest.java deleted file mode 100644 index ec739b4065..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyHttpToHttpsRedirectTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.HttpToHttpsRedirectTest; - -public class NettyHttpToHttpsRedirectTest extends HttpToHttpsRedirectTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyIdleStateHandlerTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyIdleStateHandlerTest.java deleted file mode 100644 index 26dc2a2dbe..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyIdleStateHandlerTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.IdleStateHandlerTest; - -public class NettyIdleStateHandlerTest extends IdleStateHandlerTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyInputStreamTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyInputStreamTest.java deleted file mode 100644 index 5854ddc98f..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyInputStreamTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.InputStreamTest; - -public class NettyInputStreamTest extends InputStreamTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyListenableFutureTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyListenableFutureTest.java deleted file mode 100644 index 723fc12d59..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyListenableFutureTest.java +++ /dev/null @@ -1,26 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ListenableFutureTest; - -public class NettyListenableFutureTest extends ListenableFutureTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMaxConnectionsInThreads.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMaxConnectionsInThreads.java deleted file mode 100644 index 0e24c3d55d..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMaxConnectionsInThreads.java +++ /dev/null @@ -1,23 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010-2012 Sonatype, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Apache License v2.0 which accompanies this distribution. - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * The Apache License v2.0 is available at - * http://www.apache.org/licenses/LICENSE-2.0.html - * You may elect to redistribute this code under either of these licenses. - *******************************************************************************/ -package org.asynchttpclient.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.MaxConnectionsInThreads; - -public class NettyMaxConnectionsInThreads extends MaxConnectionsInThreads { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMaxTotalConnectionTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMaxTotalConnectionTest.java deleted file mode 100644 index 15e0892a69..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMaxTotalConnectionTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.MaxTotalConnectionTest; - -public class NettyMaxTotalConnectionTest extends MaxTotalConnectionTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMultipartUploadTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMultipartUploadTest.java deleted file mode 100644 index fdbfb52d13..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMultipartUploadTest.java +++ /dev/null @@ -1,29 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.MultipartUploadTest; - -/** - * @author dominict - */ -public class NettyMultipartUploadTest extends MultipartUploadTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMultipleHeaderTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMultipleHeaderTest.java deleted file mode 100644 index 2198b1b1f0..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMultipleHeaderTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.MultipleHeaderTest; - -public class NettyMultipleHeaderTest extends MultipleHeaderTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyNoNullResponseTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyNoNullResponseTest.java deleted file mode 100644 index d6b0122263..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyNoNullResponseTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.NoNullResponseTest; - -public class NettyNoNullResponseTest extends NoNullResponseTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyNonAsciiContentLengthTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyNonAsciiContentLengthTest.java deleted file mode 100644 index 50fca62df0..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyNonAsciiContentLengthTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.NonAsciiContentLengthTest; - -public class NettyNonAsciiContentLengthTest extends NonAsciiContentLengthTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyParamEncodingTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyParamEncodingTest.java deleted file mode 100644 index d633d37979..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyParamEncodingTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ParamEncodingTest; - -public class NettyParamEncodingTest extends ParamEncodingTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPerRequestRelative302Test.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPerRequestRelative302Test.java deleted file mode 100644 index b0fabf1ef6..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPerRequestRelative302Test.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PerRequestRelative302Test; - -public class NettyPerRequestRelative302Test extends PerRequestRelative302Test { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPerRequestTimeoutTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPerRequestTimeoutTest.java deleted file mode 100644 index 960a039d9a..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPerRequestTimeoutTest.java +++ /dev/null @@ -1,34 +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.providers.netty; - -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PerRequestTimeoutTest; - -public class NettyPerRequestTimeoutTest extends PerRequestTimeoutTest { - - @Override - protected void checkTimeoutMessage(String message) { - assertTrue(message.startsWith("Request timed out"), "error message indicates reason of error"); - assertTrue(message.contains("127.0.0.1"), "error message contains remote ip address"); - assertTrue(message.contains("of 100 ms"), "error message contains timeout configuration value"); - } - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPostRedirectGetTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPostRedirectGetTest.java deleted file mode 100644 index 979c761bd7..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPostRedirectGetTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PostRedirectGetTest; - -public class NettyPostRedirectGetTest extends PostRedirectGetTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPostWithQSTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPostWithQSTest.java deleted file mode 100644 index c244b89c0b..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPostWithQSTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PostWithQSTest; - -public class NettyPostWithQSTest extends PostWithQSTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyProviderUtil.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyProviderUtil.java deleted file mode 100644 index b6120976bf..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyProviderUtil.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.DefaultAsyncHttpClient; - -public class NettyProviderUtil { - - public static AsyncHttpClient nettyProvider(AsyncHttpClientConfig config) { - if (config == null) { - config = new AsyncHttpClientConfig.Builder().build(); - } - return new DefaultAsyncHttpClient(new NettyAsyncHttpProvider(config), config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyProxyTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyProxyTest.java deleted file mode 100644 index 7c381c111d..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyProxyTest.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ProxyTest; - -public class NettyProxyTest extends ProxyTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyProxyTunnellingTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyProxyTunnellingTest.java deleted file mode 100644 index 2d47058543..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyProxyTunnellingTest.java +++ /dev/null @@ -1,28 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ProxyTunnellingTest; - -public class NettyProxyTunnellingTest extends ProxyTunnellingTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - public String getProviderClass() { - return NettyAsyncHttpProvider.class.getName(); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPutLargeFileTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPutLargeFileTest.java deleted file mode 100644 index 550847c062..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPutLargeFileTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PutLargeFileTest; - -public class NettyPutLargeFileTest extends PutLargeFileTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyQueryParametersTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyQueryParametersTest.java deleted file mode 100644 index 7a38145ef1..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyQueryParametersTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.QueryParametersTest; - -public class NettyQueryParametersTest extends QueryParametersTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRC10KTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRC10KTest.java deleted file mode 100644 index 17f26efbb8..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRC10KTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.RC10KTest; - -public class NettyRC10KTest extends RC10KTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRedirectConnectionUsageTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRedirectConnectionUsageTest.java deleted file mode 100644 index cd62bb347e..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRedirectConnectionUsageTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.RedirectConnectionUsageTest; - -public class NettyRedirectConnectionUsageTest extends RedirectConnectionUsageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRelative302Test.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRelative302Test.java deleted file mode 100644 index 22fca88ad9..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRelative302Test.java +++ /dev/null @@ -1,25 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.Relative302Test; - -public class NettyRelative302Test extends Relative302Test { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRemoteSiteTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRemoteSiteTest.java deleted file mode 100644 index 94c6f0d7aa..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRemoteSiteTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.RemoteSiteTest; - -public class NettyRemoteSiteTest extends RemoteSiteTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRequestThrottleTimeoutTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRequestThrottleTimeoutTest.java deleted file mode 100644 index 6c333eae1b..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRequestThrottleTimeoutTest.java +++ /dev/null @@ -1,148 +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.providers.netty; - -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.asynchttpclient.async.AbstractBasicTest; -import org.eclipse.jetty.continuation.Continuation; -import org.eclipse.jetty.continuation.ContinuationSupport; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Future; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -public class NettyRequestThrottleTimeoutTest extends AbstractBasicTest { - private static final String MSG = "Enough is enough."; - private static final int SLEEPTIME_MS = 1000; - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new SlowHandler(); - } - - private class SlowHandler extends AbstractHandler { - public void handle(String target, Request baseRequest, HttpServletRequest request, final HttpServletResponse response) - throws IOException, ServletException { - response.setStatus(HttpServletResponse.SC_OK); - final Continuation continuation = ContinuationSupport.getContinuation(request); - continuation.suspend(); - new Thread(new Runnable() { - public void run() { - try { - Thread.sleep(SLEEPTIME_MS); - response.getOutputStream().print(MSG); - response.getOutputStream().flush(); - continuation.complete(); - } catch (InterruptedException e) { - logger.error(e.getMessage(), e); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - }).start(); - baseRequest.setHandled(true); - } - } - - @Test(groups = { "standalone", "netty_provider" }) - public void testRequestTimeout() throws IOException { - final Semaphore requestThrottle = new Semaphore(1); - - final AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setCompressionEnabled(true) - .setAllowPoolingConnections(true).setMaxConnections(1).build()); - - int samples = 10; - - try { - final CountDownLatch latch = new CountDownLatch(samples); - final List tooManyConnections = Collections.synchronizedList(new ArrayList(2)); - - for (int i = 0; i < samples; i++) { - new Thread(new Runnable() { - - public void run() { - try { - requestThrottle.acquire(); - Future responseFuture = null; - try { - responseFuture = client.prepareGet(getTargetUrl()).setRequestTimeoutInMs(SLEEPTIME_MS / 2) - .execute(new AsyncCompletionHandler() { - - @Override - public Response onCompleted(Response response) throws Exception { - return response; - } - - @Override - public void onThrowable(Throwable t) { - logger.error("onThrowable got an error", t); - try { - Thread.sleep(100); - } catch (InterruptedException e) { - } - requestThrottle.release(); - } - }); - } catch (Exception e) { - tooManyConnections.add(e); - } - - if (responseFuture != null) - responseFuture.get(); - } catch (Exception e) { - } finally { - latch.countDown(); - } - - } - }).start(); - } - - try { - latch.await(30, TimeUnit.SECONDS); - } catch (Exception e) { - fail("failed to wait for requests to complete"); - } - - for (Exception e : tooManyConnections) - logger.error("Exception while calling execute", e); - - assertTrue(tooManyConnections.isEmpty(), "Should not have any connection errors where too many connections have been attempted"); - } finally { - client.close(); - } - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRetryRequestTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRetryRequestTest.java deleted file mode 100644 index 8ea77a62f2..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRetryRequestTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file 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: - * - * 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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.RetryRequestTest; - -public class NettyRetryRequestTest extends RetryRequestTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncClientErrorBehaviourTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncClientErrorBehaviourTest.java deleted file mode 100644 index c57b8b89d4..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncClientErrorBehaviourTest.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.SimpleAsyncClientErrorBehaviourTest; - -public class NettySimpleAsyncClientErrorBehaviourTest extends SimpleAsyncClientErrorBehaviourTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - public String getProviderClass() { - return NettyAsyncHttpProvider.class.getName(); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncHttpClientTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncHttpClientTest.java deleted file mode 100644 index 0b117c9ee6..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncHttpClientTest.java +++ /dev/null @@ -1,35 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.SimpleAsyncHttpClientTest; - -public class NettySimpleAsyncHttpClientTest extends SimpleAsyncHttpClientTest { - - /** - * Not Used with {@link org.asynchttpclient.SimpleAsyncHttpClient} - * - * @param config - * @return - */ - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return null; - } - - public String getProviderClass() { - return NettyAsyncHttpProvider.class.getName(); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyTransferListenerTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyTransferListenerTest.java deleted file mode 100644 index 504607b654..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyTransferListenerTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.TransferListenerTest; - -public class NettyTransferListenerTest extends TransferListenerTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyWebDavBasicTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyWebDavBasicTest.java deleted file mode 100644 index e3f938171b..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyWebDavBasicTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.WebDavBasicTest; - -public class NettyWebDavBasicTest extends WebDavBasicTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyZeroCopyFileTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyZeroCopyFileTest.java deleted file mode 100644 index 3e98117d0d..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyZeroCopyFileTest.java +++ /dev/null @@ -1,24 +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.providers.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ZeroCopyFileTest; - -public class NettyZeroCopyFileTest extends ZeroCopyFileTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/RetryNonBlockingIssue.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/RetryNonBlockingIssue.java deleted file mode 100644 index c5d1798595..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/RetryNonBlockingIssue.java +++ /dev/null @@ -1,225 +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.providers.netty; - -import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.async.util.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.async.AbstractBasicTest; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; - -//FIXME there's no retry actually -public class RetryNonBlockingIssue extends AbstractBasicTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - server = newJettyHttpServer(port1); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/"); - context.addServlet(new ServletHolder(new MockExceptionServlet()), "/*"); - - server.setHandler(context); - server.start(); - } - - protected String getTargetUrl() { - return String.format("http://127.0.0.1:%d/", port1); - } - - private ListenableFuture testMethodRequest(AsyncHttpClient client, int requests, String action, String id) throws IOException { - Request r = new RequestBuilder("GET")// - .setUrl(getTargetUrl())// - .addQueryParam(action, "1")// - .addQueryParam("maxRequests", "" + requests)// - .addQueryParam("id", id)// - .build(); - return client.executeRequest(r); - } - - /** - * Tests that a head request can be made - * - * @throws IOException - * @throws ExecutionException - * @throws InterruptedException - */ - @Test - public void testRetryNonBlocking() throws IOException, InterruptedException, ExecutionException { - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setAllowPoolingConnections(true)// - .setMaxConnections(100)// - .setConnectionTimeout(60000)// - .setRequestTimeout(30000)// - .build(); - - AsyncHttpClient client = getAsyncHttpClient(config); - try { - List> res = new ArrayList>(); - for (int i = 0; i < 32; i++) { - res.add(testMethodRequest(client, 3, "servlet", UUID.randomUUID().toString())); - } - - StringBuilder b = new StringBuilder(); - for (ListenableFuture r : res) { - Response theres = r.get(); - assertEquals(200, theres.getStatusCode()); - b.append("==============\r\n"); - b.append("Response Headers\r\n"); - Map> heads = theres.getHeaders(); - b.append(heads + "\r\n"); - b.append("==============\r\n"); - assertTrue(heads.size() > 0); - } - System.out.println(b.toString()); - System.out.flush(); - - } finally { - client.close(); - } - } - - @Test - public void testRetryNonBlockingAsyncConnect() throws IOException, InterruptedException, ExecutionException { - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setAllowPoolingConnections(true)// - .setMaxConnections(100)// - .setConnectionTimeout(60000)// - .setRequestTimeout(30000)// - .build(); - - AsyncHttpClient client = getAsyncHttpClient(config); - - try { - List> res = new ArrayList>(); - for (int i = 0; i < 32; i++) { - res.add(testMethodRequest(client, 3, "servlet", UUID.randomUUID().toString())); - } - - StringBuilder b = new StringBuilder(); - for (ListenableFuture r : res) { - Response theres = r.get(); - assertEquals(theres.getStatusCode(), 200); - b.append("==============\r\n"); - b.append("Response Headers\r\n"); - Map> heads = theres.getHeaders(); - b.append(heads + "\r\n"); - b.append("==============\r\n"); - assertTrue(heads.size() > 0); - } - System.out.println(b.toString()); - System.out.flush(); - - } finally { - client.close(); - } - } - - @SuppressWarnings("serial") - public class MockExceptionServlet extends HttpServlet { - - private Map requests = new ConcurrentHashMap(); - - private synchronized int increment(String id) { - int val = 0; - if (requests.containsKey(id)) { - Integer i = requests.get(id); - val = i + 1; - requests.put(id, val); - } else { - requests.put(id, 1); - val = 1; - } - System.out.println("REQUESTS: " + requests); - return val; - } - - public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { - String maxRequests = req.getParameter("maxRequests"); - int max = 0; - try { - max = Integer.parseInt(maxRequests); - } catch (NumberFormatException e) { - max = 3; - } - String id = req.getParameter("id"); - int requestNo = increment(id); - String servlet = req.getParameter("servlet"); - String io = req.getParameter("io"); - String error = req.getParameter("500"); - - if (requestNo >= max) { - res.setHeader("Success-On-Attempt", "" + requestNo); - res.setHeader("id", id); - if (servlet != null && servlet.trim().length() > 0) - res.setHeader("type", "servlet"); - if (error != null && error.trim().length() > 0) - res.setHeader("type", "500"); - if (io != null && io.trim().length() > 0) - res.setHeader("type", "io"); - res.setStatus(200); - res.setContentLength(0); - res.flushBuffer(); - return; - } - - res.setStatus(200); - res.setContentLength(100); - res.setContentType("application/octet-stream"); - res.flushBuffer(); - - // error after flushing the status - if (servlet != null && servlet.trim().length() > 0) - throw new ServletException("Servlet Exception"); - - if (io != null && io.trim().length() > 0) - throw new IOException("IO Exception"); - - if (error != null && error.trim().length() > 0) { - res.sendError(500, "servlet process was 500"); - } - } - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyByteMessageTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyByteMessageTest.java deleted file mode 100644 index 695db23d5b..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyByteMessageTest.java +++ /dev/null @@ -1,25 +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.providers.netty.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.NettyProviderUtil; -import org.asynchttpclient.websocket.ByteMessageTest; - -public class NettyByteMessageTest extends ByteMessageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyCloseCodeReasonMsgTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyCloseCodeReasonMsgTest.java deleted file mode 100644 index deec16902c..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyCloseCodeReasonMsgTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 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.providers.netty.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.NettyProviderUtil; -import org.asynchttpclient.websocket.CloseCodeReasonMessageTest; - -public class NettyCloseCodeReasonMsgTest extends CloseCodeReasonMessageTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyProxyTunnellingTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyProxyTunnellingTest.java deleted file mode 100644 index 2ce2156d96..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyProxyTunnellingTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.providers.netty.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.NettyProviderUtil; -import org.asynchttpclient.websocket.ProxyTunnellingTest; -import org.testng.annotations.Test; - -@Test -public class NettyProxyTunnellingTest extends ProxyTunnellingTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyRedirectTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyRedirectTest.java deleted file mode 100644 index 185ffb1806..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyRedirectTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 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.providers.netty.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.NettyProviderUtil; -import org.asynchttpclient.websocket.RedirectTest; - -public class NettyRedirectTest extends RedirectTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyTextMessageTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyTextMessageTest.java deleted file mode 100644 index c1286255ad..0000000000 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyTextMessageTest.java +++ /dev/null @@ -1,25 +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.providers.netty.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.NettyProviderUtil; -import org.asynchttpclient.websocket.TextMessageTest; - -public class NettyTextMessageTest extends TextMessageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty/src/test/resources/300k.png b/providers/netty/src/test/resources/300k.png deleted file mode 100644 index bff4a85989..0000000000 Binary files a/providers/netty/src/test/resources/300k.png and /dev/null differ diff --git a/providers/netty/src/test/resources/SimpleTextFile.txt b/providers/netty/src/test/resources/SimpleTextFile.txt deleted file mode 100644 index 088788f821..0000000000 --- a/providers/netty/src/test/resources/SimpleTextFile.txt +++ /dev/null @@ -1 +0,0 @@ -This is a simple test file \ No newline at end of file diff --git a/providers/netty/src/test/resources/client.keystore b/providers/netty/src/test/resources/client.keystore deleted file mode 100644 index eaf8339f44..0000000000 Binary files a/providers/netty/src/test/resources/client.keystore and /dev/null differ diff --git a/providers/netty/src/test/resources/gzip.txt.gz b/providers/netty/src/test/resources/gzip.txt.gz deleted file mode 100644 index 80aeb98d2b..0000000000 Binary files a/providers/netty/src/test/resources/gzip.txt.gz and /dev/null differ diff --git a/providers/netty/src/test/resources/logback-test.xml b/providers/netty/src/test/resources/logback-test.xml deleted file mode 100644 index 4acf278710..0000000000 --- a/providers/netty/src/test/resources/logback-test.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - %d [%thread] %level %logger - %m%n - - - - - - - - - \ No newline at end of file diff --git a/providers/netty/src/test/resources/realm.properties b/providers/netty/src/test/resources/realm.properties deleted file mode 100644 index bc9faad66a..0000000000 --- a/providers/netty/src/test/resources/realm.properties +++ /dev/null @@ -1 +0,0 @@ -user=admin, admin \ No newline at end of file diff --git a/providers/netty/src/test/resources/ssltest-cacerts.jks b/providers/netty/src/test/resources/ssltest-cacerts.jks deleted file mode 100644 index 207b9646e6..0000000000 Binary files a/providers/netty/src/test/resources/ssltest-cacerts.jks and /dev/null differ diff --git a/providers/netty/src/test/resources/ssltest-keystore.jks b/providers/netty/src/test/resources/ssltest-keystore.jks deleted file mode 100644 index 70267836e8..0000000000 Binary files a/providers/netty/src/test/resources/ssltest-keystore.jks and /dev/null differ diff --git a/providers/netty/src/test/resources/textfile.txt b/providers/netty/src/test/resources/textfile.txt deleted file mode 100644 index 87daee60a9..0000000000 --- a/providers/netty/src/test/resources/textfile.txt +++ /dev/null @@ -1 +0,0 @@ -filecontent: hello \ No newline at end of file diff --git a/providers/netty/src/test/resources/textfile2.txt b/providers/netty/src/test/resources/textfile2.txt deleted file mode 100644 index 6a91fe609c..0000000000 --- a/providers/netty/src/test/resources/textfile2.txt +++ /dev/null @@ -1 +0,0 @@ -filecontent: hello2 \ No newline at end of file diff --git a/providers/pom.xml b/providers/pom.xml deleted file mode 100644 index 72af625a7f..0000000000 --- a/providers/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - org.asynchttpclient - async-http-client-project - 2.0.0-SNAPSHOT - - 4.0.0 - async-http-client-providers-parent - Asynchronous Http Client Providers Parent - pom - - The Async Http Client providers library parent. - - - - - - org.apache.felix - maven-bundle-plugin - 2.3.4 - true - - META-INF - - - $(replace;$(project.version);-SNAPSHOT;.$(tstamp;yyyyMMdd-HHmm)) - - Sonatype - - - - - osgi-bundle - package - - bundle - - - - - - - - - grizzly - netty - - - - - org.asynchttpclient - async-http-client-api - ${project.version} - - - org.asynchttpclient - async-http-client-api - ${project.version} - test - tests - - - diff --git a/site/pom.xml b/site/pom.xml deleted file mode 100644 index b6e29bd130..0000000000 --- a/site/pom.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - org.asynchttpclient - async-http-client-project - 2.0.0-SNAPSHOT - - 4.0.0 - org.asynchttpclient - async-http-client-site - Asynchronous Http Client Project Site - 2.0.0-SNAPSHOT - pom - - The Async Http Client site. - - \ No newline at end of file diff --git a/site/src/site/apt/auth.apt b/site/src/site/apt/auth.apt deleted file mode 100644 index 567830b3a6..0000000000 --- a/site/src/site/apt/auth.apt +++ /dev/null @@ -1,39 +0,0 @@ - ------ - Async Http Client - Configuring Authentication: BASIC, DIGEST or NTLM - ------ - Jeanfrancois Arcand - ------ - 2012 - -Configuring Authentication: BASIC, DIGEST or NTLM - - Configuring authentication with AsyncHttpClient is simple. You can configure it at the <<>> level using the - <<>>: - -+-----+ -AsyncHttpClient client = new AsyncHttpClient(); -Realm realm = new Realm.RealmBuilder() - .setPrincipal(user) - .setPassword(admin) - .setUsePreemptiveAuth(true) - .setScheme(AuthScheme.BASIC) - .build(); -client.prepareGet("http://.../").setRealm(realm).execute(); -+-----+ - - You can also set the realm at the AsyncHttpClientConfig level: - -+-----+ -Builder builder = new AsyncHttpClientConfig.Builder(); -Realm realm = new Realm.RealmBuilder() - .setPrincipal(user) - .setPassword(admin) - .setUsePreemptiveAuth(true) - .setScheme(AuthScheme.BASIC) - .build(); -builder.setRealm(realm).build(); -AsyncHttpClient client = new AsyncHttpClient(builder.build()); -+-----+ - - The authentication type supported are <<>>, <<>> and <<>>. You can also customize your own - authentication mechanism by using the Response Filter. diff --git a/site/src/site/apt/configuring.apt b/site/src/site/apt/configuring.apt deleted file mode 100644 index 6eecccc888..0000000000 --- a/site/src/site/apt/configuring.apt +++ /dev/null @@ -1,50 +0,0 @@ - ------ - Async Http Client - Configuring the AsyncHttpClient - ------ - Jeanfrancois Arcand - ------ - 2012 - -Configuring the AsyncHttpClient. - - You can configure the <<>> class using the <<>>'s Builder: - -+-----+ -Builder builder = new AsyncHttpClientConfig.Builder(); -builder.setCompressionEnabled(true) - .setAllowPoolingConnection(true) - .setRequestTimesout(30000) - .build(); - -AsyncHttpClient client = new AsyncHttpClient(builder.build()); -+-----+ - - You can set the ExecutorServices as well if you don't want to use the default, which is a cached threads pool: - -+-----+ -Builder builder = new AsyncHttpClientConfig.Builder(); -builder.setExecutorService(myOwnThreadPool); -AsyncHttpClient client = new AsyncHttpClient(builder.build()); -+-----+ - - You can also configure the connection pool the library is using and implement your own polling strategy: - -+-----+ -Builder builder = new AsyncHttpClientConfig.Builder(); -builder.setConnectionsPool(new ConnectionsPoo() { - public boolean offer(U uri, V connection) {...} - - public V poll(U uri) {...} - - public boolean removeAll(V connection) {...} - - public boolean canCacheConnection() {...} - - public void destroy() {...} -}); -AsyncHttpClient client = new AsyncHttpClient(builder.build()); -+-----+ - - It is recommended to use the default connections pool for performance reason, but you are always free to design a better one. - - You can also set the SSL information, Filters, etc. Those topics will be covered inside their own section. diff --git a/site/src/site/apt/filters.apt b/site/src/site/apt/filters.apt deleted file mode 100644 index 797b071e1b..0000000000 --- a/site/src/site/apt/filters.apt +++ /dev/null @@ -1,151 +0,0 @@ - ------ - Async Http Client - Using Filters - ------ - Jeanfrancois Arcand - ------ - 2012 - -Using Filters - - The library supports three types of <<>> who can intercept, transform, decorate and replay transactions: - <<>>, <<>> and <<>>. - -* Request Filter - - Request Filters are useful if you need to manipulate the Request or AsyncHandler object before the request is made. As an example, you can throttle requests using the following RequestFilter implementation: - -+-----+ -public class ThrottleRequestFilter implements RequestFilter { - private final int maxConnections; - private final Semaphore available; - private final int maxWait; - - public ThrottleRequestFilter(int maxConnections) { - this.maxConnections = maxConnections; - this.maxWait = Integer.MAX_VALUE; - available = new Semaphore(maxConnections, true); - } - - public ThrottleRequestFilter(int maxConnections, int maxWait) { - this.maxConnections = maxConnections; - this.maxWait = maxWait; - available = new Semaphore(maxConnections, true); - } - - public FilterContext filter(FilterContext ctx) throws FilterException { - try { - if (!available.tryAcquire(maxWait, TimeUnit.MILLISECONDS)) - throw new FilterException(String.format("No slot available for Request %s with AsyncHandler %s", - ctx.getRequest(), - ctx.getAsyncHandler())); - } - } catch (InterruptedException e) { - throw new FilterException( String.format("Interrupted Request %s with AsyncHandler %s", - ctx.getRequest(), - ctx.getAsyncHandler())); - } - - return new FilterContext(new AsyncHandlerWrapper(ctx.getAsyncHandler()), ctx.getRequest()); - } - -} - -private class AsyncHandlerWrapper implements AsyncHandler { - private final AsyncHandler asyncHandler; - - public AsyncHandlerWrapper(AsyncHandler asyncHandler) { - this.asyncHandler = asyncHandler; - } - - public void onThrowable(Throwable t) { - asyncHandler.onThrowable(t); - } - - public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - return asyncHandler.onBodyPartReceived(bodyPart); - } - - public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { - return asyncHandler.onStatusReceived(responseStatus); - } - - public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { - return asyncHandler.onHeadersReceived(headers); } - - public T onCompleted() throws Exception { - available.release(); - return asyncHandler.onCompleted(); - } -} -+-----+ - - In the above, we decorate the original <<>> and use semaphore to throttle requests. - To add <<>>, all you need to do is to configure it on the <<>>: - -+-----+ -AsyncHttpClientConfig.Builder b = new AsyncHttpClientConfig.Builder(); -b.addRequestFilter(new ThrottleRequestFilter(100)); -AsyncHttpClient c = new AsyncHttpClient(b.build()); -+-----+ - - * Response Filter - - Like with <<>>, you can also filter the <<>>'s bytes before an <<>> gets called. - <<>> are always invoked before the library executes the logic for authentication, proxy challenging, - redirection etc. That means an application can takes control of those operations at any moment using a <<>>. - - As an example, the following <<>> redirect request from <<>> to <<>> in case - <<<.ca>>> is not responding: - -+-----+ -AsyncHttpClientConfig.Builder b = new AsyncHttpClientConfig.Builder(); -b.addResponseFilter(new ResponseFilter() { - public FilterContext filter(FilterContext ctx) throws FilterException { - if (ctx.getResponseStatus().getStatusCode() == 503) { - return new FilterContext.FilterContextBuilder(ctx) - .request(new RequestBuilder("GET") - .setUrl("http://google.com/").build()) - .build(); - } - } -}); -AsyncHttpClient c = new AsyncHttpClient(b.build()); -+-----+ - -* IOException Filter - - The AsyncHttpClient library support <<>> that can be used to replay a request in case server a - server goes down or unresponsive, a network outage occurs, or nay kind of I/O abnormal situation. - - In those cases, the library will catch the <<>> and delegate the <<>> handling to the <<>>. - - As an example, the following filter will resume an interrupted download instead of restarting downloading the file - from the beginning: - -+-----+ -AsyncHttpClient c = new AsyncHttpClient( - new AsyncHttpClientConfig.Builder() - .addIOExceptionFilter(new ResumableIOExceptionFilter()).build()); - -Response r = c.prepareGet("http://host:port/LargeFile.avi").execute(new AsyncHandler(){...}).get(); -+-----+ - - The <<>> is defined as - -+-----+ -public class ResumableIOExceptionFilter implements IOExceptionFilter { - public FilterContext filter(FilterContext ctx) throws FilterException { - if (ctx.getIOException() != null ) { - Request request = new RequestBuilder(ctx.getRequest()).setRangeOffset(file.length()); - return new FilterContext.FilterContextBuilder(ctx) - .request(request) - .replayRequest(true) - .build(); - } - return ctx; - } -} -+-----+ - -In the above we just catch any <<>> and replay the request using the <<>> header to tell the remote -server to restart sending bytes at that position. This way we don't need to re download the entire file. diff --git a/site/src/site/apt/oauth.apt b/site/src/site/apt/oauth.apt deleted file mode 100644 index fb8e9bab22..0000000000 --- a/site/src/site/apt/oauth.apt +++ /dev/null @@ -1,26 +0,0 @@ - ------ - Async Http Client - Using OAuth - ------ - Jeanfrancois Arcand - ------ - 2012 - -Using OAuth - - You can use the library to pull data from any OAuth site (like Twitter). This is as simple as: - -+-----+ -private static final String CONSUMER_KEY = "dpf43f3p2l4k3l03"; -private static final String CONSUMER_SECRET = "kd94hf93k423k f44"; -public static final String TOKEN_KEY = "nnch734d00sl2jdk"; -public static final String TOKEN_SECRET = "pfkkdhi9sl3r4s00"; -public static final String NONCE = "kllo9940pd9333jh"; -final static long TIMESTAMP = 1191242096; - -public void oAuth() { -ConsumerKey consumer = new ConsumerKey(CONSUMER_KEY, CONSUMER_SECRET); -RequestToken user = new RequestToken(TOKEN_KEY, TOKEN_SECRET); -OAuthSignatureCalculator calc = new OAuthSignatureCalculator(consumer, user); -AsyncHttpClient client = new AsyncHttpClient(); -Response response = client.prepareGet("http://.../").setSignatureCalculator(calc).execute().get(); -+-----+ diff --git a/site/src/site/apt/performances.apt b/site/src/site/apt/performances.apt deleted file mode 100644 index 6d26b77af0..0000000000 --- a/site/src/site/apt/performances.apt +++ /dev/null @@ -1,22 +0,0 @@ - ------ - Async Http Client - Limiting the number of connections to improve raw performance - ------ - Jeanfrancois Arcand - ------ - 2012 - -Limiting the number of connections to improve raw performance - - By default the library uses a connection pool and re-use connections as needed. It is important to not let the - connection pool grow too large as it takes resources in memory. One way consist of setting the maximum number of - connection per host or in total: - -+-----+ -AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() - .setMaximumConnectionsPerHost(10) - .setMaximumConnectionsTotal(100) - .build(); -AsyncHttpClient c = new AsyncHttpClient(config); -+-----+ - - There is no magic number, so you will need to try it and decide which one gives the best result. diff --git a/site/src/site/apt/providers.apt b/site/src/site/apt/providers.apt deleted file mode 100644 index 1ececa1037..0000000000 --- a/site/src/site/apt/providers.apt +++ /dev/null @@ -1,38 +0,0 @@ - ------ - Async Http Client - Switching Provider - ------ - Jeanfrancois Arcand - ------ - 2012 - -Switching Provider - - By default, the <<>> is using the powerful {{{http://netty.io/}Netty}}'s framework as the - HTTP processor. There might be environment where you can't use Netty. Fortunately, the <<>> library - supports two other http runtime: the <<>>, which build around the <<>>, and - <<>> which build on top of the - {{{http://hc.apache.org/httpcomponents-client-ga/index.html}Apache HttpClient}}. - - To change provider, all you need to do is: - -+-----+ -AsyncHttpClient client = new AsyncHttpClient(new ApacheAsyncHttpProvider(new AsyncHttpClientConfig.Builder().build())); -+-----+ - - Same for the JDK: - -+-----+ -AsyncHttpclient client = new AsyncHttpClient(new JDKAsyncHttpProvider(new AsyncHttpClientConfig.Builder().build())); -+-----+ - - Also every <<>> can be configured with their native functionality. - - As an example, you can switch the <<>> to use blocking I/O instead of NIO: - -+-----+ -NettyAsyncHttpProviderConfig config = new NettyAsyncHttpProviderConfig(); -config.setProperty(NettyAsyncHttpProviderConfig.USE_BLOCKING_IO, "true"); - -AsyncHttpClientConfig c = new AsyncHttpClientConfig().setAsyncHttpClientProviderConfig(config).build(); -AsyncHttpClient client = new AsyncHttpClient(new NettyAsyncHttpProvider(config)); -+-----+ diff --git a/site/src/site/apt/proxy.apt b/site/src/site/apt/proxy.apt deleted file mode 100644 index a09932fc0e..0000000000 --- a/site/src/site/apt/proxy.apt +++ /dev/null @@ -1,86 +0,0 @@ - ------ - Async Http Client - Configuring a Proxy - ------ - Jeanfrancois Arcand - ------ - 2012 - -Configuring a Proxy - - The AsyncHttpClient library supports proxy, proxy authentication and proxy tunneling. - Just need to create a <<>> instance: - -+-----+ -AsyncHttpClient client = new AsyncHttpClient(); -Future f = client.prepareGet("http://....) - .setProxyServer(new ProxyServer("127.0.0.1", 8080)) - .execute(); -+-----+ - - If you need to use an SSL tunnel, all you need to do is: - -+-----+ -ProxyServer ps = new ProxyServer(ProxyServer.Protocol.HTTPS, "127.0.0.1", 8080); -AsyncHttpClient asyncHttpClient = new AsyncHttpClient(); -RequestBuilder rb = new RequestBuilder("GET") - .setProxyServer(ps) - .setUrl("https://twitpic.com/"); -Future responseFuture = asyncHttpClient.executeRequest(rb.build(), new AsyncCompletionHandlerBase() { - @Override - public void onThrowable(Throwable t) {} - - @Override - public Response onCompleted(Response response) throws Exception { - return response; - } - -}); -Response r = responseFuture.get(); -+-----+ - - You can also set the authentication token on the <<>> instance: - -+-----+ -ProxyServer ps = new ProxyServer(ProxyServer.Protocol.HTTPS, "127.0.0.1", 8080, "admin", "password"); -AsyncHttpClient asyncHttpClient = new AsyncHttpClient(); -RequestBuilder rb = new RequestBuilder("GET") - .setProxyServer(ps) - .setUrl("https://twitpic.com/"); -Future responseFuture = asyncHttpClient.executeRequest(rb.build(), new AsyncCompletionHandlerBase() { - @Override - public void onThrowable(Throwable t) {} - - @Override - public Response onCompleted(Response response) throws Exception { - return response; - } - -}); -Response r = responseFuture.get(); -+-----+ - - You can also set the <<>> at the <<>> level. In that case, all request will share - the same proxy information. - -Using Java System Properties - - The AsyncHttpClient library supports the standard - {{{http://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html#Proxies}Java Proxy System Properties}}. - You can configure this at a global level using the <<>> method on the - <<>>, or by setting the <<>> - system property to true. - -Using JDK ProxySelectors - - The AsyncHttpClient library also supports using the default - {{{http://docs.oracle.com/javase/7/docs/api/java/net/ProxySelector.html}JDK ProxySelector}}. This allows for more - fine grained control over which proxies to use, for example, it can be used in combination with - {{{https://code.google.com/p/proxy-vole/}Proxy Vole}} to use OS configured proxies or to use a proxy.pac file. - - You configure this at a global level using the <<>> method on the - <<>>, or by setting the - <<>> system property to true. - - If you don't change the default JDK <<>>, this setting is very similar to the <<>> - setting, though the <<>> setting does allow more flexibility, such as the ability to use an - HTTPS proxy. \ No newline at end of file diff --git a/site/src/site/apt/request.apt b/site/src/site/apt/request.apt deleted file mode 100644 index 09e6e358d4..0000000000 --- a/site/src/site/apt/request.apt +++ /dev/null @@ -1,180 +0,0 @@ - ------ - Async Http Client - Executing request - ------ - Jeanfrancois Arcand - ------ - 2012 - -Executing request synchronously or asynchronously. - - The first thing to decide when using the library is if your application can handle asynchronous response or not. - If not, the library has been designed using the Future API, hence you can always execute synchronous call by blocking - on the <<>> method: - -+-----+ -AsyncHttpClient client = new AsyncHttpClient(); -Response response = client.prepareGet(("http://sonatype.com/").execute().get(); -+-----+ - - The above means the request will block until the full Response has been received. It also made your application's - blocking, waiting for the response to comes back. This could be potentially an issue to block for every request, - specially when doing <<>> or <<>> operations where you don't necessarily need to wait for the response. - A simple way consist of not calling the <<>> - -+-----+ -AsyncHttpClient client = new AsyncHttpClient(); -Response response = client.preparePut(("http://sonatype.com/myFile.avi").execute(); -+-----+ - - A better way than above would consist of using an AsyncHandler. The AynchHandler API is fairly simple and just - consists of 5 methods to implements: - -+-----+ -public interface AsyncHandler { - void onThrowable(Throwable t); - - STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) - throws Exception; - - STATE onStatusReceived(HttpResponseStatus responseStatus) - throws Exception; - - STATE onHeadersReceived(HttpResponseHeaders headers) - throws Exception; - - T onCompleted() throws Exception; -} -+-----+ - -* Creating a Request object - - The AsynHttpClient uses the builder pattern when it is time to create Request object. The simplest way consist of: - -+-----+ -RequestBuilder builder = new RequestBuilder("PUT"); -Request request = builder..setUrl("http://") - .addHeader("name", "value") - .setBody(new File("myUpload.avi")) - .build(); -AsyncHttpClient client = new AsyncHttpClient(); -client.execute(request, new AsyncHandler<...>() { -..... -} ); -+-----+ - - If you need to work with File, the library supports the {{{./zero-bytes-copy.html}zero copy in memory concept}}, - e.g the File can be uploaded or downloaded without loading its associated bytes in memory, preventing out of memory - errors in case you need to upload or download many large files. Although the library support the following: - -+-----+ -Request request = builder.setUrl("http://") - .addHeader("name", "value") - .setBody(myInputStream)) - .build(); -+-----+ - - it is discouraged to use <<>> as the library will need to buffer bytes in memory in order to determine - the length of the stream, and instead highly recommended to either use a File or the <<>> API to avoid - loading unnecessary bytes in memory: - -+-----+ -public interface BodyGenerator { - Body createBody() throws IOException; -} -+-----+ - - where a Body is defined as: - -+-----+ -public interface Body { - long getContentLength(); - - long read(ByteBuffer buffer) - throws IOException; - - void close() throws IOException; -} -+-----+ - - This way the library will never read unnecessary bytes in memory, which could significantly improve the performance - your application. - - The <<>> can also be used to create per <<>> configuration, - like setting a Proxy or request timeout: - -+-----+ -PerRequestConfig requestConfig = new PerRequestConfig(); -requestConfig.setRequestTimeoutInMs(5 * 1000); -requestConfig.setProxy(new ProxyServer(...)); -Future responseFuture = client.prepareGet("http://").setPerRequestConfig(requestConfig).execute(); -+-----+ - -* Creating a Response object - - The AsyncHandler is typed, e.g you can return any object from the <<>>. One useful object - of the library is the <<>> object and it's associate builder. You can incrementally create a <<>> - object using the <<>> method: - -+-----+ -MyAsyncHandler asyncHandler = new MyAsyncHanfler() { - private final Response.ResponseBuilder builder = new Response.ResponseBuilder(); - - public STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { - builder.accumulate(content); - return STATE.CONTINUE; - } - - public STATE onStatusReceived(final HttpResponseStatus status) throws Exception { - builder.accumulate(status); - return STATE.CONTINUE; - } - - public STATE onHeadersReceived(final HttpResponseHeaders headers) throws Exception { - builder.accumulate(headers); - return STATE.CONTINUE; - } - - public Response onCompleted() throws Exception { - return builder.build(); - } - -} - -Response response = client.prepareGet("http://sonatype.com/").execute(asyncHandler).get(); -+-----+ - - One thing to consider when creating a <<>> object is the size of the response body. By default, - a <<>> object will accumulate all response's bytes in memory, and that could potentially create an out of - memory error. If you are planning to use the API for downloading large files, it is not recommended to accumulate - bytes in memory and instead flush the bytes on disk as soon as they are available. Note that you can still use the - <<>> object, except you don't accumulate the response's bytes as demonstrated below: - -+-----+ -MyAsyncHandler asyncHandler = new MyAsyncHanfler() { - private final Response.ResponseBuilder builder = new Response.ResponseBuilder(); - - public STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { - content.write(myOutputStream); - return STATE.CONTINUE; - } - - public STATE onStatusReceived(final HttpResponseStatus status) throws Exception { - builder.accumulate(status); return STATE.CONTINUE; - } - - public STATE onHeadersReceived(final HttpResponseHeaders headers) throws Exception { - builder.accumulate(headers); - return STATE.CONTINUE; - } - - public Response onCompleted() throws Exception { - return builder.build(); - } - -} - -Response response = client.prepareGet("http://sonatype.com/").execute(asyncHandler).get(); -+-----+ - - Note that in the above scenario invoking <<>> or <<>> will - return an <<>> because the body wasn't accumulated by the <<>> object. diff --git a/site/src/site/apt/resumable-download.apt b/site/src/site/apt/resumable-download.apt deleted file mode 100644 index 22ba329a9e..0000000000 --- a/site/src/site/apt/resumable-download.apt +++ /dev/null @@ -1,75 +0,0 @@ - ------ - Async Http Client - Resumable Dowload - ------ - Jeanfrancois Arcand - ------ - 2012 - -Uploading file: Progress Listener - - The AsyncHttpClient supports resumable download in two different scenarios: - - * <<>>: If an <<>> occurs (for whatever reason), you can configure the library to restart - the download automatically without having to restart the download from the beginning. - - * <<>>: If your application or the JVM goes down during a file download, the library can also restart the - download automatically when the same download is requested. - - - You can configure the <<>> Library to survive <<> using the <<>>: - -+-----+ -AsyncHttpClient c = new AsyncHttpClient(new AsyncHttpClientConfig.Builder() - .addIOExceptionFilter(new ResumableIOExceptionFilter()).build()); -ResumableAsyncHandler a = new ResumableAsyncHandler(new ResumableRandomAccessFileListener()); -a.setResumableListener(new ResumableRandomAccessFileListener(new RandomAccessFile("file.avi", "rw"))); -Response r = c.prepareGet("http://host:port/file.avi").execute(a).get(); -+-----+ - - If you need something more high level and configurable, you can use a <<>>, and or implement - a <<>>: - -+-----+ -AsyncHttpClient c = new AsyncHttpClient(); -ResumableAsyncHandler a = new ResumableAsyncHandler(new PropertiesBasedResumableProcessor()); -a.setResumableListener(new ResumableRandomAccessFileListener(new RandomAccessFile("file.avi", "rw"))); -Response r = c.prepareGet( "http://localhost:8081/file.AVI" ).execute( a ).get(); -+-----+ - - You can also simply use a <<>> (or use the <<>>, - which does what's described below): - -+-----+ -public interface ResumableListener { - public void onBytesReceived(ByteBuffer byteBuffer) throws IOException; - - public void onAllBytesReceived(); - - public long length(); -} -+-----+ - - As simple as: - -+-----+ -AsyncHttpClient c = new AsyncHttpClient(); -final RandomAccessFile file = new RandomAccessFile("file.avi", "rw"); -ResumableAsyncHandler a = new ResumableAsyncHandler(); -a.setResumableListener(new ResumableListener() { - - public void onBytesReceived(ByteBuffer byteBuffer) throws IOException { - file.seek(file.length()); - file.write(byteBuffer.array()); - } - - public void onAllBytesReceived() { - file.close(); - } - - public long length() { - return file.length(); - } - -}); -Response r = c.prepareGet( "http://localhost:8081/file.AVI" ).execute( a ).get(); -+-----+ diff --git a/site/src/site/apt/ssl.apt b/site/src/site/apt/ssl.apt deleted file mode 100644 index aeb3e958a9..0000000000 --- a/site/src/site/apt/ssl.apt +++ /dev/null @@ -1,40 +0,0 @@ - ------ - Async Http Client - Configuring SSL - ------ - Jeanfrancois Arcand - ------ - 2012 - -Configuring SSL - - Configuring the library to support SSL is simple. By default you don't have to configure anything if you don't need to use your own certificates etc. - -+-----+ -AsyncHttpClient client = new AsyncHttpClient(); -Response response = client.prepareGet(("https://sonatype.com/").execute().get(); -+-----+ - - The library will detect it's an SSL request and appropriately locate the key store, trust store etc. - If you need to configure those objects, all you need to do is to create an <<>> and set it using the - <<>>'s Builder as showed below: - -+-----+ -InputStream keyStoreStream = .... -char[] keyStorePassword = "changeit".toCharArray(); -KeyStore ks = KeyStore.getInstance("JKS"); -ks.load(keyStoreStream, keyStorePassword); - -char[] certificatePassword = "changeit".toCharArray(); -KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); -kmf.init(ks, certificatePassword); - -KeyManager[] keyManagers = kmf.getKeyManagers(); -TrustManager[] trustManagers = new TrustManager[]{DUMMY_TRUST_MANAGER}; -SecureRandom secureRandom = new SecureRandom(); - -SSLContext sslContext = SSLContext.getInstance("TLS"); -sslContext.init(keyManagers, trustManagers, secureRandom); -Builder builder = new AsyncHttpClientConfig.Builder(); -builder.setSSLContext(sslContext); -AsyncHttpClient client = new AsyncHttpClient(builder.build()); -+-----+ diff --git a/site/src/site/apt/transfer-listener.apt b/site/src/site/apt/transfer-listener.apt deleted file mode 100644 index 399ea96492..0000000000 --- a/site/src/site/apt/transfer-listener.apt +++ /dev/null @@ -1,34 +0,0 @@ - ------ - Async Http Client - TransferListener - ------ - Jeanfrancois Arcand - ------ - 2012 - -TransferListener - - In some scenario an application may need to manipulate the received bytes in more than one place, e.g. saves the bytes - on disk but also accumulate it for checksum checking later. In that case, instead of using an <<>> and - mixes logic inside an <<>>, it is recommended to use the <<>> - simple API: - -+-----+ -public interface TransferListener { - public void onRequestHeadersSent(FluentCaseInsensitiveStringsMap headers); - public void onResponseHeadersReceived (FluentCaseInsensitiveStringsMap headers); - public void onBytesReceived(ByteBuffer buffer) throws IOException; - public void onBytesSent(ByteBuffer buffer); - public void onRequestResponseCompleted(); - public void onThrowable(Throwable t); -} -+-----+ - - All you need to do in that case is to create a <<>> and add as many <<>> - as you need: - -+-----+ -AsyncHttpClient client = new AsyncHttpClient(); -TransferCompletionHandler tl = new TransferCompletionHandler(); -tl.addTransferListener(new TransferListener(){...}); -Response response = httpClient.prepareGet("http://.../").execute(tl).get(); -+-----+ diff --git a/site/src/site/apt/upload.apt b/site/src/site/apt/upload.apt deleted file mode 100644 index 65da26e440..0000000000 --- a/site/src/site/apt/upload.apt +++ /dev/null @@ -1,32 +0,0 @@ - ------ - Async Http Client - Uploading file: Progress Listener - ------ - Jeanfrancois Arcand - ------ - 2012 - -Uploading file: Progress Listener - - When uploading bytes, an application might need to take some action depending on where the upload status is. - - The AsyncHttpClient library support a special <<>> called <<>> that can be used to - track the upload operation: - -+-----+ -public interface ProgressAsyncHandler extends AsyncHandler { - STATE onHeaderWriteCompleted(); - STATE onContentWriteCompleted(); - STATE onContentWriteProgress(long amount, long current, long total); -} -+-----+ - - The methods are called in the following order: - - * <<>>: invoked when the headers has been flushed to the remote server - - * <<>>: as soon as some response's body bytes are written. Might be invoked many times. - - * <<>>: invoked when the response has been sent or aborted. - - - Like with <<>>, you can always always abort the processing at any moment in the upload process. diff --git a/site/src/site/apt/webdav.apt b/site/src/site/apt/webdav.apt deleted file mode 100644 index e20d3fa2b7..0000000000 --- a/site/src/site/apt/webdav.apt +++ /dev/null @@ -1,25 +0,0 @@ - ------ - Async Http Client - Using the WebDav protocol - ------ - Jeanfrancois Arcand - ------ - 2012 - -Using the WebDav protocol - - The <<>> has build in support for the WebDav protocol. The API can be used the same way normal HTTP - request are made, and everything discussed in this documentation works with WebDAV as well: - -+-----+ -AsyncHttpClient c = new AsyncHttpClient(); -Request mkcolRequest = new RequestBuilder("MKCOL").setUrl("http://host:port/folder1").build(); -Response response = c.executeRequest(mkcolRequest).get(); -+-----+ - - or - -+-----+ -AsyncHttpClient c = new AsyncHttpClient(); -Request propFindRequest = new RequestBuilder("PROPFIND").setUrl("http://host:port).build(); -Response response = c.executeRequest(propFindRequest, new AsyncHandler(){...}).get(); -+-----+ diff --git a/site/src/site/apt/zero-bytes-copy.apt b/site/src/site/apt/zero-bytes-copy.apt deleted file mode 100644 index 0d16cd7683..0000000000 --- a/site/src/site/apt/zero-bytes-copy.apt +++ /dev/null @@ -1,45 +0,0 @@ - ------ - Async Http Client - Zero Bytes Copy - ------ - Jeanfrancois Arcand - ------ - 2012 - -Zero Bytes Copy - - When uploading or downloading bytes, it is important to try to avoid buffering bytes in memory. - -* Upload - - On the upload side, the mechanism is enabled by default when setting the <<>>'s body to a File: - -+-----+ -AsyncHttpClient client = new AsyncHttpClient(); -File file = new File("file.avi"); -Future f = client.preparePut("http://localhost/").setBody(file).execute(); -+-----+ - - If you can't use a File, the recommended way is to use a <<>>. It is strongly recommended to avoid - using <<>> as the library will unfortunately buffer the entire content in memory in order to set the - <<>>, which can cause out of memory error. - -* Download - - On the download side, you can use the <<>> to avoid loading bytes in memory and - unnecessary copy: - -+-----+ -AsyncHttpClient client = new AsyncHttpClient(); -File tmp = new File("zeroCopy.txt"); -final FileOutputStream stream = new FileOutputStream(tmp); -Future f = client.prepareGet("http://localhost/largefile.avi").execute(new AsyncHandler() { - public void onThrowable(Throwable t) { } - - public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { - bodyPart.writeTo(stream); - return STATE.CONTINUE; - } - -{ .... } }); -Response resp = f.get(); -+-----+ diff --git a/site/src/site/site.xml b/site/src/site/site.xml deleted file mode 100644 index 1602c44f3f..0000000000 --- a/site/src/site/site.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - org.apache.maven.skins - maven-fluido-skin - 1.1 - - - - - true - false - - jfarcand - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/site/src/site/xdoc/index.xml.vm b/site/src/site/xdoc/index.xml.vm deleted file mode 100644 index 6860f4dd76..0000000000 --- a/site/src/site/xdoc/index.xml.vm +++ /dev/null @@ -1,28 +0,0 @@ - - - - - Async Http Client - Home - Jeanfrancois Arcand - - - -
-

Welcome to the Async Http Client!

-

The Async Http Client library purpose is to allow Java applications to - easily execute HTTP requests and asynchronously process the HTTP responses. - The library also supports the WebSocket Protocol.

-
- -
-

The Async HTTP Client library is simple to use. First, in order to add it to your Maven project, simply add - this dependency:

- - ${project.groupId} - ${project.artifactId} - ${project.version} -]]> -
- -