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/MIGRATION.md b/MIGRATION.md deleted file mode 100644 index 43781ca2cb..0000000000 --- a/MIGRATION.md +++ /dev/null @@ -1,51 +0,0 @@ -Migration Guide ---------------- - -## From 1.8 to 1.9 - -AsyncHttpClient v1.9 is a preview of v2, so it comes with some breaking changes. - -* Target JDK7, drop support for JDK5 and JDK6 -* Rename many AsyncHttpClientConfig parameters: - * `maxTotalConnections` becomes `maxConnections` - * `maxConnectionPerHost` becomes `maxConnectionsPerHost` - * `connectionTimeOutInMs` becomes `connectTimeout` - * `webSocketIdleTimeoutInMs` becomes `webSocketTimeout` - * `idleConnectionInPoolTimeoutInMs` becomes `pooledConnectionIdleTimeout` - * `idleConnectionTimeoutInMs` becomes `readTimeout` - * `requestTimeoutInMs` becomes `requestTimeout` - * `maxConnectionLifeTimeInMs` becomes `connectionTTL` - * `redirectEnabled` becomes `followRedirect` - * `allowPoolingConnection` becomes `allowPoolingConnections` - * `allowSslConnectionPool` becomes `allowPoolingSslConnections` - * `connectionTimeout` becomes `connectTimeout` - * `compressionEnabled` becomes `compressionEnforced` (default true) so it's always enabled and can honor user defined Accept-Encoding - * `requestCompressionLevel` was dropped, as it wasn't working - * `SSLEngineFactory` was moved to Netty config as only Netty honors it - * `useRawUrl` becomes `disableUrlEncodingForBoundedRequests`, as it's only honored by bound requests - * `getAllowPoolingConnection` becomes `isAllowPoolingConnection` -* Drop `PerRequestConfig`. `requestTimeOut` and `proxy` can now be directly set on the request. -* Drop `java.net.URI` in favor of own `com.ning.http.client.uri.Uri`. You can use `toJavaNetURI` to convert. -* Drop `Proxy.getURI` in favor of `getUrl` -* Drop deprecated methods: `Request` and `RequestBuilderBase`'s `getReqType` in favor of `getMethod`, `Request.getLength` in favor of `getContentLength` -* Drop deprecated `RealmBuilder.getDomain` in favor of `getNtlmDomain` -* Rename `xxxParameter` (add, set, get...) into `xxxFormParam` -* Rename `xxxQueryParameter` (add, set, get...) into `xxxQueryParam` -* Merge `boolean Request.isRedirectEnabled` and `boolean isRedirectOverrideSet` are merged into `Boolean isRedirectEnabled` -* Remove url parameter from `SignatureCalculator.calculateAndAddSignature`, as it can be fetched on the request parameter -* Rename `com.ning.http.client.websocket` package into `com.ning.http.client.ws` -* WebSocket Listeners now have to implement proper interfaces to be notified or fragment events: `WebSocketByteFragmentListener` and `WebSocketTextFragmentListener` -* Rename WebSocket's `sendTextMessage` into `sendMessage` and `streamText` into `stream` -* Rename NettyAsyncHttpProviderConfig's `handshakeTimeoutInMillis` into `handshakeTimeout` -* Netty provider now creates SslEngines instances with proper hoststring and port. -* Parts, Realm and ProxyServer now take a java.nio.Charset instead of a String charset name -* New AsyncHandlerExtensions methods: - * `onOpenConnection`, - * `onConnectionOpen`, - * `onPoolConnection`, - * `onConnectionPooled`, - * `onSendRequest`, - * `onDnsResolved`, - * `onSslHandshakeCompleted` -* Rename FluentCaseInsensitiveStringsMap and FluentStringsMap `replace` into `replaceWith` to not conflict with new JDK8 Map methods -* execute no longer throws Exceptions, all of them are notified to the handler/future diff --git a/README.md b/README.md index 35a48e0a45..0272134ed1 100644 --- a/README.md +++ b/README.md @@ -1,243 +1,263 @@ -Async Http Client ([@AsyncHttpClient](https://twitter.com/AsyncHttpClient) on twitter) ---------------------------------------------------- +# 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) -[Javadoc](http://www.javadoc.io/doc/com.ning/async-http-client/) +Follow [@AsyncHttpClient](https://twitter.com/AsyncHttpClient) on Twitter. -[Getting](https://jfarcand.wordpress.com/2010/12/21/going-asynchronous-using-asynchttpclient-the-basic/) [started](https://jfarcand.wordpress.com/2011/01/04/going-asynchronous-using-asynchttpclient-the-complex/), and use [WebSockets](http://jfarcand.wordpress.com/2011/12/21/writing-websocket-clients-using-asynchttpclient/) +The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. +The library also supports the WebSocket Protocol. -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. +It's built on top of [Netty](https://github.com/netty/netty). It's compiled with Java 11. ## Installation -First, in order to add it to your Maven project, simply add this dependency: +Binaries are deployed on Maven Central. +Add a dependency on the main AsyncHttpClient artifact: +Maven: ```xml - - com.ning - async-http-client - 1.9.29 - + + + org.asynchttpclient + async-http-client + 3.0.2 + + ``` -You can also download the artifact - -[Maven Search](http://search.maven.org) - -AHC is an abstraction layer that can work on top of the bare JDK, Netty and Grizzly. -Note that the JDK implementation is very limited and you should **REALLY** use the other *real* providers. +Gradle: +```groovy +dependencies { + implementation 'org.asynchttpclient:async-http-client:3.0.2' +} +``` -You then have to add the Netty or Grizzly jars in the classpath. +### Dsl -For Netty: +Import the Dsl helpers to use convenient methods to bootstrap components: -```xml - - io.netty - netty - LATEST_NETTY_3_VERSION - +```java +import static org.asynchttpclient.Dsl.*; ``` -For Grizzly: +### Client -```xml - - org.glassfish.grizzly - connection-pool - LATEST_GRIZZLY_VERSION - - - org.glassfish.grizzly - grizzly-websockets - LATEST_GRIZZLY_VERSION - +```java +import static org.asynchttpclient.Dsl.*; + +AsyncHttpClient asyncHttpClient=asyncHttpClient(); ``` -Check [migration guide](MIGRATION.md) for migrating from 1.8 to 1.9. +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. -## Usage +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. + +## Configuration -Then in your code you can simply do +Finally, you can also configure the AsyncHttpClient instance via its AsyncHttpClientConfig object: ```java -import com.ning.http.client.*; -import java.util.concurrent.Future; +import static org.asynchttpclient.Dsl.*; -AsyncHttpClient asyncHttpClient = new AsyncHttpClient(); -Future f = asyncHttpClient.prepareGet("http://www.ning.com/").execute(); -Response r = f.get(); +AsyncHttpClient c=asyncHttpClient(config().setProxyServer(proxyServer("127.0.0.1",38080))); ``` -Note that in this case all the content must be read fully in memory, even if you used `getResponseBodyAsStream()` method on returned `Response` object. +## HTTP -You can also accomplish asynchronous (non-blocking) operation without using a Future if you want to receive and process the response in your handler: +### 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 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 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); ``` -(this will also fully read `Response` in memory before calling `onCompleted`) +#### Setting Request Body -You can also mix Future with AsyncHandler to only retrieve part of the asynchronous response +Use the `setBody` method to add a body to the request. -```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(); -``` +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 -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. +Use the `addBodyPart` method to add a multipart part to the request. - 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: +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 -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(); +Future whenResponse=asyncHttpClient.prepareGet("http://www.example.com/").execute(); + Response response=whenResponse.get(); ``` -## Configuration +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! -Finally, you can also configure the AsyncHttpClient via its AsyncHttpClientConfig object: +### Setting callbacks on the ListenableFuture + +`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 -AsyncHttpClientConfig cf = new AsyncHttpClientConfig.Builder() - S.setProxyServer(new ProxyServer("127.0.0.1", 38080)).build(); -AsyncHttpClient c = new AsyncHttpClient(cf); + 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); ``` -## WebSocket +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. -Async Http Client also support WebSocket by simply doing: +The below sample just capture the response status and skips processing the response body chunks. + +Note that returning `ABORT` closes the underlying connection. ```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("...").sendMessage("..."); - } - - @Override - public void onClose(WebSocket websocket) { - latch.countDown(); - } - - @Override - public void onError(Throwable t) { - } - }).build()).get(); +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(); ``` -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) +#### Using Continuations + +`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 -AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().build(); -AsyncHttpClient client = new AsyncHttpClient(new GrizzlyAsyncHttpProvider(config), config); +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 ``` -## User Group +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) -Keep up to date on the library development by joining the Asynchronous HTTP Client discussion group +## WebSocket -[Google Group](http://groups.google.com/group/asynchttpclient) +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 9ed7e23397..0000000000 --- a/api/pom.xml +++ /dev/null @@ -1,46 +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. - - - - - - maven-jar-plugin - - - - test-jar - - - - - - maven-antrun-plugin - - - process-classes - - run - - - - - - - - - - - - - - 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 1b8cf53184..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncCompletionHandler.java +++ /dev/null @@ -1,114 +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.handler.ProgressAsyncHandler; -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 static final Logger LOGGER = LoggerFactory.getLogger(AsyncCompletionHandler.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) { - 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 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 onHeadersWritten() { - 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 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 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 d149516054..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 static final Logger LOGGER = LoggerFactory.getLogger(AsyncCompletionHandlerBase.class); - - /** - * {@inheritDoc} - */ - @Override - public Response onCompleted(Response response) throws Exception { - return response; - } - - /** - * {@inheritDoc} - */ - @Override - public void onThrowable(Throwable t) { - LOGGER.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 de332fb527..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncHandler.java +++ /dev/null @@ -1,111 +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/AsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClient.java deleted file mode 100755 index 841592e662..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClient.java +++ /dev/null @@ -1,263 +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.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#getReadTimeout()} - * 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 - */ - ListenableFuture executeRequest(Request request, AsyncHandler handler); - - /** - * Execute an HTTP request. - * - * @param request {@link Request} - * @return a {@link Future} of type Response - */ - ListenableFuture executeRequest(Request request); -} 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 9ce13cc65a..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ /dev/null @@ -1,1224 +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.config.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.SSLContext; - -import org.asynchttpclient.channel.SSLEngineFactory; -import org.asynchttpclient.filter.IOExceptionFilter; -import org.asynchttpclient.filter.RequestFilter; -import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.proxy.ProxyServerSelector; -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 { - - public final static String AHC_VERSION; - - static { - InputStream is = null; - Properties prop = new Properties(); - try { - is = AsyncHttpClientConfig.class.getResourceAsStream("/ahc-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 connectTimeout; - - 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 boolean acceptAnyCertificate; - - protected boolean followRedirect; - protected int maxRedirects; - protected boolean strict302Handling; - - protected ProxyServerSelector proxyServerSelector; - - protected boolean compressionEnforced; - 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 String[] enabledProtocols; - protected String[] enabledCipherSuites; - protected Integer sslSessionCacheSize; - protected Integer sslSessionTimeout; - protected int httpClientCodecMaxInitialLineLength = 4096; - protected int httpClientCodecMaxHeaderSize = 8192; - protected int httpClientCodecMaxChunkSize = 8192; - protected boolean disableZeroCopy; - protected long handshakeTimeout = 10000L; - protected SSLEngineFactory sslEngineFactory; - protected int chunkedFileChunkSize = 8192; - protected int webSocketMaxBufferSize = 128000000; - protected int webSocketMaxFrameSize = 10 * 1024; - protected boolean keepEncodingHeader = false; - protected AsyncHttpProviderConfig providerConfig; - - protected AsyncHttpClientConfig() { - } - - private AsyncHttpClientConfig(int connectTimeout,// - int maxConnections,// - int maxConnectionsPerHost,// - int requestTimeout,// - int readTimeout,// - int webSocketTimeout,// - boolean allowPoolingConnection,// - boolean allowSslConnectionPool,// - int idleConnectionInPoolTimeout,// - int maxConnectionLifeTime,// - SSLContext sslContext, // - boolean acceptAnyCertificate, // - boolean followRedirect, // - int maxRedirects, // - boolean strict302Handling, // - ExecutorService applicationThreadPool,// - ProxyServerSelector proxyServerSelector, // - boolean compressionEnforced, // - String userAgent,// - Realm realm,// - List requestFilters,// - List responseFilters,// - List ioExceptionFilters,// - int maxRequestRetry, // - boolean disableUrlEncodingForBoundRequests, // - int ioThreadMultiplier, // - String[] enabledProtocols,// - String[] enabledCipherSuites,// - Integer sslSessionCacheSize,// - Integer sslSessionTimeout,// - int httpClientCodecMaxInitialLineLength,// - int httpClientCodecMaxHeaderSize,// - int httpClientCodecMaxChunkSize,// - boolean disableZeroCopy,// - long handshakeTimeout,// - SSLEngineFactory sslEngineFactory,// - int chunkedFileChunkSize,// - int webSocketMaxBufferSize,// - int webSocketMaxFrameSize,// - boolean keepEncodingHeader,// - AsyncHttpProviderConfig providerConfig) { - - this.connectTimeout = connectTimeout; - 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.acceptAnyCertificate = acceptAnyCertificate; - this.followRedirect = followRedirect; - this.maxRedirects = maxRedirects; - this.strict302Handling = strict302Handling; - this.proxyServerSelector = proxyServerSelector; - this.compressionEnforced = compressionEnforced; - 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.enabledProtocols = enabledProtocols; - this.enabledCipherSuites = enabledCipherSuites; - this.sslSessionCacheSize = sslSessionCacheSize; - this.sslSessionTimeout = sslSessionTimeout; - this.providerConfig = providerConfig; - this.httpClientCodecMaxInitialLineLength = httpClientCodecMaxInitialLineLength; - this.httpClientCodecMaxHeaderSize = httpClientCodecMaxHeaderSize; - this.httpClientCodecMaxChunkSize = httpClientCodecMaxChunkSize; - this.disableZeroCopy = disableZeroCopy; - this.handshakeTimeout = handshakeTimeout; - this.sslEngineFactory = sslEngineFactory; - this.chunkedFileChunkSize = chunkedFileChunkSize; - this.webSocketMaxBufferSize = webSocketMaxBufferSize; - this.webSocketMaxFrameSize = webSocketMaxFrameSize; - this.keepEncodingHeader = keepEncodingHeader; - } - - /** - * 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 getConnectTimeout() { - return connectTimeout; - } - - /** - * Return the maximum time, in milliseconds, a - * {@link org.asynchttpclient.ws.WebSocket} may be idle before being timed - * out. - * - * @return the maximum time, in milliseconds, a - * {@link org.asynchttpclient.ws.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} waits - * until the response is completed. - * - * @return the maximum time in millisecond an {@link AsyncHttpClient} waits - * until the response is completed. - */ - 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 enforced. - * - * @return true if compression is enforced - */ - public boolean isCompressionEnforced() { - return compressionEnforced; - } - - /** - * 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 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 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; - } - - /** - * 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; - } - - public boolean isAcceptAnyCertificate() { - return acceptAnyCertificate; - } - - /** - * since 1.9.0 - */ - public String[] getEnabledProtocols() { - return enabledProtocols; - } - - /** - * since 1.9.0 - */ - public String[] getEnabledCipherSuites() { - return enabledCipherSuites; - } - - /** - * since 1.9.13 - */ - public Integer getSslSessionCacheSize() { - return sslSessionCacheSize; - } - - /** - * since 1.9.13 - */ - public Integer getSslSessionTimeout() { - return sslSessionTimeout; - } - - public int getHttpClientCodecMaxInitialLineLength() { - return httpClientCodecMaxInitialLineLength; - } - - public int getHttpClientCodecMaxHeaderSize() { - return httpClientCodecMaxHeaderSize; - } - - public int getHttpClientCodecMaxChunkSize() { - return httpClientCodecMaxChunkSize; - } - - public boolean isDisableZeroCopy() { - return disableZeroCopy; - } - - public long getHandshakeTimeout() { - return handshakeTimeout; - } - - public SSLEngineFactory getSslEngineFactory() { - return sslEngineFactory; - } - - public int getChunkedFileChunkSize() { - return chunkedFileChunkSize; - } - - public int getWebSocketMaxBufferSize() { - return webSocketMaxBufferSize; - } - - public int getWebSocketMaxFrameSize() { - return webSocketMaxFrameSize; - } - - public boolean isKeepEncodingHeader() { - return keepEncodingHeader; - } - - /** - * Builder for an {@link AsyncHttpClient} - */ - public static class Builder { - private int connectTimeout = defaultConnectTimeout(); - 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 boolean acceptAnyCertificate = defaultAcceptAnyCertificate(); - private boolean followRedirect = defaultFollowRedirect(); - private int maxRedirects = defaultMaxRedirects(); - private boolean strict302Handling = defaultStrict302Handling(); - private ProxyServerSelector proxyServerSelector = null; - private boolean useProxySelector = defaultUseProxySelector(); - private boolean useProxyProperties = defaultUseProxyProperties(); - private boolean compressionEnforced = defaultCompressionEnforced(); - 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 String[] enabledProtocols = defaultEnabledProtocols(); - private String[] enabledCipherSuites; - private Integer sslSessionCacheSize = defaultSslSessionCacheSize(); - private Integer sslSessionTimeout = defaultSslSessionTimeout(); - private int httpClientCodecMaxInitialLineLength = defaultHttpClientCodecMaxInitialLineLength(); - private int httpClientCodecMaxHeaderSize = defaultHttpClientCodecMaxHeaderSize(); - private int httpClientCodecMaxChunkSize = defaultHttpClientCodecMaxChunkSize(); - private boolean disableZeroCopy = defaultDisableZeroCopy(); - private long handshakeTimeout = defaultHandshakeTimeout(); - private SSLEngineFactory sslEngineFactory; - private int chunkedFileChunkSize = defaultChunkedFileChunkSize(); - private int webSocketMaxBufferSize = defaultWebSocketMaxBufferSize(); - private int webSocketMaxFrameSize = defaultWebSocketMaxFrameSize(); - private boolean keepEncodingHeader = defaultKeepEncodingHeader(); - private AsyncHttpProviderConfig providerConfig; - - 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 (scheme, host, port) an - * {@link AsyncHttpClient} can handle. - * - * @param maxConnectionsPerHost the maximum number of connections per - * (scheme, host, port) 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 connectTimeout the maximum time in millisecond an - * {@link AsyncHttpClient} can wait when connecting to a - * remote host - * @return a {@link Builder} - */ - public Builder setConnectTimeout(int connectTimeout) { - this.connectTimeout = connectTimeout; - return this; - } - - /** - * Set the maximum time in millisecond an - * {@link org.asynchttpclient.ws.WebSocket} can stay idle. - * - * @param webSocketTimeout the maximum time in millisecond an - * {@link org.asynchttpclient.ws.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} waits - * until the response is completed. - * - * @param requestTimeout the maximum time in millisecond an - * {@link AsyncHttpClient} waits until the response is - * completed. - * @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; - } - - /** - * Enforce HTTP compression. - * - * @param compressionEnabled true if compression is enforced - * @return a {@link Builder} - */ - public Builder setCompressionEnforced(boolean compressionEnforced) { - this.compressionEnforced = compressionEnforced; - 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; - } - - /** - * 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; - } - - /** - * 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; - } - - public Builder setAcceptAnyCertificate(boolean acceptAnyCertificate) { - this.acceptAnyCertificate = acceptAnyCertificate; - return this; - } - - public Builder setEnabledProtocols(String[] enabledProtocols) { - this.enabledProtocols = enabledProtocols; - return this; - } - - public Builder setEnabledCipherSuites(String[] enabledCipherSuites) { - this.enabledCipherSuites = enabledCipherSuites; - return this; - } - - public Builder setSslSessionCacheSize(Integer sslSessionCacheSize) { - this.sslSessionCacheSize = sslSessionCacheSize; - return this; - } - - public Builder setSslSessionTimeout(Integer sslSessionTimeout) { - this.sslSessionTimeout = sslSessionTimeout; - 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 setDisableZeroCopy(boolean disableZeroCopy) { - this.disableZeroCopy = disableZeroCopy; - return this; - } - - public Builder setHandshakeTimeout(long handshakeTimeout) { - this.handshakeTimeout = handshakeTimeout; - return this; - } - - public Builder setSslEngineFactory(SSLEngineFactory sslEngineFactory) { - this.sslEngineFactory = sslEngineFactory; - return this; - } - - public Builder setChunkedFileChunkSize(int chunkedFileChunkSize) { - this.chunkedFileChunkSize = chunkedFileChunkSize; - return this; - } - - public Builder setWebSocketMaxBufferSize(int webSocketMaxBufferSize) { - this.webSocketMaxBufferSize = webSocketMaxBufferSize; - return this; - } - - public Builder setWebSocketMaxFrameSize(int webSocketMaxFrameSize) { - this.webSocketMaxFrameSize = webSocketMaxFrameSize; - return this; - } - - public Builder setKeepEncodingHeader(boolean keepEncodingHeader) { - this.keepEncodingHeader = keepEncodingHeader; - 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(); - connectTimeout = prototype.getConnectTimeout(); - 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(); - compressionEnforced = prototype.isCompressionEnforced(); - 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(); - strict302Handling = prototype.isStrict302Handling(); - acceptAnyCertificate = prototype.acceptAnyCertificate; - enabledProtocols = prototype.enabledProtocols; - enabledCipherSuites = prototype.enabledCipherSuites; - sslSessionCacheSize = prototype.sslSessionCacheSize; - sslSessionTimeout = prototype.sslSessionTimeout; - httpClientCodecMaxInitialLineLength = prototype.httpClientCodecMaxInitialLineLength; - httpClientCodecMaxHeaderSize = prototype.httpClientCodecMaxHeaderSize; - httpClientCodecMaxChunkSize = prototype.httpClientCodecMaxChunkSize; - disableZeroCopy = prototype.disableZeroCopy; - handshakeTimeout = prototype.handshakeTimeout; - sslEngineFactory = prototype.sslEngineFactory; - chunkedFileChunkSize = prototype.chunkedFileChunkSize; - webSocketMaxBufferSize = prototype.webSocketMaxBufferSize; - webSocketMaxFrameSize = prototype.webSocketMaxFrameSize; - keepEncodingHeader = prototype.keepEncodingHeader; - - providerConfig = prototype.getAsyncHttpProviderConfig(); - } - - /** - * 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; - - return new AsyncHttpClientConfig(connectTimeout,// - maxConnections,// - maxConnectionsPerHost,// - requestTimeout,// - readTimeout,// - webSocketTimeout,// - allowPoolingConnections,// - allowPoolingSslConnections,// - pooledConnectionIdleTimeout,// - connectionTTL,// - sslContext, // - acceptAnyCertificate, // - followRedirect, // - maxRedirects, // - strict302Handling, // - applicationThreadPool, // - proxyServerSelector, // - compressionEnforced, // - userAgent,// - realm,// - requestFilters, // - responseFilters,// - ioExceptionFilters,// - maxRequestRetry, // - disableUrlEncodingForBoundRequests, // - ioThreadMultiplier, // - enabledProtocols, // - enabledCipherSuites, // - sslSessionCacheSize, // - sslSessionTimeout, // - httpClientCodecMaxInitialLineLength, // - httpClientCodecMaxHeaderSize, // - httpClientCodecMaxChunkSize, // - disableZeroCopy, // - handshakeTimeout, // - sslEngineFactory, // - chunkedFileChunkSize, // - webSocketMaxBufferSize, // - webSocketMaxFrameSize, // - keepEncodingHeader, // - providerConfig); - } - } -} 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 0bb74e800c..0000000000 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpProvider.java +++ /dev/null @@ -1,37 +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; - -/** - * 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. - */ - ListenableFuture execute(Request request, AsyncHandler handler); - - /** - * 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/BoundRequestBuilder.java b/api/src/main/java/org/asynchttpclient/BoundRequestBuilder.java deleted file mode 100644 index ffbe7392b8..0000000000 --- a/api/src/main/java/org/asynchttpclient/BoundRequestBuilder.java +++ /dev/null @@ -1,133 +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.request.body.multipart.Part; - -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) { - return client.executeRequest(build(), handler); - } - - public ListenableFuture execute() { - 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/DefaultAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java deleted file mode 100644 index 92b65b0bc9..0000000000 --- a/api/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java +++ /dev/null @@ -1,251 +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.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.RequestFilter; -import org.asynchttpclient.handler.resumable.ResumableAsyncHandler; -import org.asynchttpclient.netty.NettyAsyncHttpProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DefaultAsyncHttpClient implements AsyncHttpClient { - - 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. - * - * 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. - * - * @param config a {@link AsyncHttpClientConfig} - */ - public DefaultAsyncHttpClient(AsyncHttpClientConfig config) { - this(new NettyAsyncHttpProvider(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) { - - if (config.getRequestFilters().isEmpty()) { - return httpProvider.execute(request, handler); - - } else { - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(handler).request(request).build(); - try { - fc = preProcessRequest(fc); - } catch (Exception e) { - handler.onThrowable(e); - return new ListenableFuture.CompletedFailure<>("preProcessRequest failed", e); - } - - return httpProvider.execute(fc.getRequest(), fc.getAsyncHandler()); - } - } - - @Override - public ListenableFuture executeRequest(Request request) { - return executeRequest(request, new AsyncCompletionHandlerBase()); - } - - /** - * 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); - if (fc == null) { - throw new NullPointerException("FilterContext is null"); - } - } - - 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; - } - - 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/FluentCaseInsensitiveStringsMap.java b/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java deleted file mode 100644 index 62d760a5df..0000000000 --- a/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java +++ /dev/null @@ -1,522 +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 replaceWith(final String key, final String... values) { - return replaceWith(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 replaceWith(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) { - replaceWith(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()) { - replaceWith(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); - - replaceWith(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.isEmpty()) { - return null; - } 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.isEmpty()) { - 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 Collections.emptyList(); - - String lcKey = key.toString().toLowerCase(Locale.ENGLISH); - String realKey = keyLookup.get(lcKey); - - return realKey != null ? values.get(realKey) : Collections. emptyList(); - } - - /** - * {@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 1c672e6365..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 replaceWith(final String key, final String... values) { - return replaceWith(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 replaceWith(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) { - replaceWith(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()) { - replaceWith(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); - - replaceWith(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 5ecc6898c3..0000000000 --- a/api/src/main/java/org/asynchttpclient/HttpResponseHeaders.java +++ /dev/null @@ -1,49 +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 2d35bdc574..0000000000 --- a/api/src/main/java/org/asynchttpclient/HttpResponseStatus.java +++ /dev/null @@ -1,113 +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.net.SocketAddress; -import java.util.List; - -import org.asynchttpclient.uri.Uri; - -/** - * A class that represent the HTTP response' status line (code + text) - */ -public abstract class HttpResponseStatus { - - private final Uri uri; - protected final AsyncHttpClientConfig config; - - public HttpResponseStatus(Uri uri, AsyncHttpClientConfig config) { - this.uri = uri; - this.config = config; - } - - /** - * Return the request {@link Uri} - * - * @return the request {@link Uri} - */ - public final Uri 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(); - - /** - * 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(); -} 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 be604aad58..0000000000 --- a/api/src/main/java/org/asynchttpclient/ListenableFuture.java +++ /dev/null @@ -1,141 +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.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * 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); - - public 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 InterruptedException, ExecutionException { - throw e; - } - - @Override - public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - throw e; - } - - @Override - public void done() { - } - - @Override - public void abort(Throwable t) { - } - - @Override - public void touch() { - } - - @Override - public ListenableFuture addListener(Runnable listener, Executor exec) { - exec.execute(listener); - return this; - } - } -} 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/Realm.java b/api/src/main/java/org/asynchttpclient/Realm.java deleted file mode 100644 index 9123693b97..0000000000 --- a/api/src/main/java/org/asynchttpclient/Realm.java +++ /dev/null @@ -1,671 +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 java.nio.charset.StandardCharsets.ISO_8859_1; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import java.nio.charset.Charset; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.concurrent.ThreadLocalRandom; - -import org.asynchttpclient.uri.Uri; -import org.asynchttpclient.util.AuthenticatorUtils; -import org.asynchttpclient.util.StringUtils; - -/** - * This class is required when authentication is needed. The class support DIGEST and BASIC. - */ -public class Realm { - - private static final String DEFAULT_NC = "00000001"; - private static final String EMPTY_ENTITY_MD5 = "d41d8cd98f00b204e9800998ecf8427e"; - - 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 Uri uri; - private final String methodName; - 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 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, Uri uri, String method, boolean usePreemptiveAuth, String ntlmDomain, Charset charset, - String host, 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.ntlmHost = host; - this.charset = charset; - this.useAbsoluteURI = useAbsoluteURI; - this.omitQuery = omitQuery; - this.targetProxy = targetProxy; - } - - public String getPrincipal() { - return principal; - } - - public String getPassword() { - return password; - } - - 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 Uri getUri() { - return uri; - } - - 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 ntlmHost; - } - - 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; - private String response; - private String opaque; - private String qop; - private String nc = DEFAULT_NC; - private String cnonce; - private 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 = false; - private boolean omitQuery; - private boolean targetProxy; - - private static final ThreadLocal digestThreadLocal = new ThreadLocal() { - @Override - protected MessageDigest initialValue() { - try { - return MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - }; - - public String getNtlmDomain() { - return ntlmDomain; - } - - public RealmBuilder setNtlmDomain(String ntlmDomain) { - this.ntlmDomain = ntlmDomain; - return this; - } - - public String getNtlmHost() { - return ntlmHost; - } - - public RealmBuilder setNtlmHost(String host) { - this.ntlmHost = 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) { - if (isNonEmpty(qop)) { - this.qop = qop; - } - return this; - } - - public String getNc() { - return nc; - } - - public RealmBuilder setNc(String nc) { - this.nc = nc; - return this; - } - - public Uri getUri() { - return uri; - } - - public RealmBuilder setUri(Uri 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 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; - } - private 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 (rawServerSupportedQop.equals("auth")) - return rawServerSupportedQop; - } - - for (String rawServerSupportedQop: serverSupportedQops) { - if (rawServerSupportedQop.equals("auth-int")) - return rawServerSupportedQop; - } - - return null; - } - - 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")); - - String rawQop = match(headerLine, "qop"); - if (rawQop != null) { - setQop(parseRawQop(rawQop)); - } - - 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")); - String algorithm = match(headerLine, "algorithm"); - if (isNonEmpty(algorithm)) { - setAlgorithm(algorithm); - } - setQop(match(headerLine, "qop")); - if (isNonEmpty(getNonce())) { - setScheme(AuthScheme.DIGEST); - } else { - setScheme(AuthScheme.BASIC); - } - setTargetProxy(true); - return this; - } - - public RealmBuilder clone(Realm clone) { - return setRealmName(clone.getRealmName())// - .setAlgorithm(clone.getAlgorithm())// - .setMethodName(clone.getMethodName())// - .setNc(clone.getNc())// - .setNonce(clone.getNonce())// - .setPassword(clone.getPassword())// - .setPrincipal(clone.getPrincipal())// - .setCharset(clone.getCharset())// - .setOpaque(clone.getOpaque())// - .setQop(clone.getQop())// - .setScheme(clone.getScheme())// - .setUri(clone.getUri())// - .setUsePreemptiveAuth(clone.getUsePreemptiveAuth())// - .setNtlmDomain(clone.getNtlmDomain())// - .setNtlmHost(clone.getNtlmHost())// - .setUseAbsoluteURI(clone.isUseAbsoluteURI())// - .setOmitQuery(clone.isOmitQuery())// - .setTargetProxy(clone.isTargetProxy()); - } - - 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 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; - } - - public Charset getCharset() { - return charset; - } - - public RealmBuilder setCharset(Charset charset) { - this.charset = charset; - return this; - } - - private byte[] md5FromRecycledStringBuilder(StringBuilder sb, MessageDigest md) { - md.update(StringUtils.charSequence2ByteBuffer(sb, ISO_8859_1)); - sb.setLength(0); - return md.digest(); - } - - private byte[] secretDigest(StringBuilder sb, MessageDigest md) { - - sb.append(principal).append(':').append(realmName).append(':').append(password); - byte[] ha1 = md5FromRecycledStringBuilder(sb, md); - - if (algorithm == null || algorithm.equals("MD5")) { - return ha1; - } else if ("MD5-sess".equals(algorithm)) { - appendBase16(sb, ha1); - sb.append(':').append(nonce).append(':').append(cnonce); - return md5FromRecycledStringBuilder(sb, md); - } - - throw new UnsupportedOperationException("Digest algorithm not supported: " + algorithm); - } - - private byte[] dataDigest(StringBuilder sb, String digestUri, MessageDigest md) { - - sb.append(methodName).append(':').append(digestUri); - if ("auth-int".equals(qop)) { - sb.append(':').append(EMPTY_ENTITY_MD5); - - } else if (qop != null && !qop.equals("auth")) { - throw new UnsupportedOperationException("Digest qop not supported: " + qop); - } - - return md5FromRecycledStringBuilder(sb, md); - } - - private void appendDataBase(StringBuilder sb) { - 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) { - // BEWARE: compute first as it used the cached StringBuilder - String digestUri = AuthenticatorUtils.computeRealmURI(uri, useAbsoluteURI, omitQuery); - - StringBuilder sb = StringUtils.stringBuilder(); - - // WARNING: DON'T MOVE, BUFFER IS RECYCLED!!!! - byte[] secretDigest = secretDigest(sb, md); - byte[] dataDigest = dataDigest(sb, digestUri, md); - - appendBase16(sb, secretDigest); - appendDataBase(sb); - appendBase16(sb, dataDigest); - - byte[] responseDigest = md5FromRecycledStringBuilder(sb, md); - response = toHexString(responseDigest); - } - - private static String toHexString(byte[] data) { - StringBuilder buffer = StringUtils.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 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); - } - } - - /** - * Build a {@link Realm} - * - * @return a {@link Realm} - */ - public Realm build() { - - // Avoid generating - if (isNonEmpty(nonce)) { - MessageDigest md = digestThreadLocal.get(); - newCnonce(md); - newResponse(md); - } - - return new Realm(scheme, principal, password, realmName, nonce, algorithm, response, qop, nc, cnonce, uri, methodName, - usePreemptive, ntlmDomain, charset, ntlmHost, 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 99473eb999..0000000000 --- a/api/src/main/java/org/asynchttpclient/Request.java +++ /dev/null @@ -1,208 +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.File; -import java.io.InputStream; -import java.net.InetAddress; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.Collection; -import java.util.List; - -import org.asynchttpclient.channel.NameResolver; -import org.asynchttpclient.channel.pool.ConnectionPoolPartitioning; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.request.body.generator.BodyGenerator; -import org.asynchttpclient.request.body.multipart.Part; -import org.asynchttpclient.uri.Uri; - -/** - * 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(); - - Uri getUri(); - - String getUrl(); - - /** - * 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 composite of byte arrays - */ - List getCompositeByteData(); - - /** - * 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 a ByteBuffer - * - * @return a ByteBuffer - */ - ByteBuffer getByteBufferData(); - - /** - * 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 List} 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 List} 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 getRequestTimeout(); - - /** - * Return the HTTP Range header value, or - * - * @return the range header value, or 0 is not set. - */ - long getRangeOffset(); - - /** - * Return the charset value used when decoding the request's body. - * - * @return the charset value used when decoding the request's body. - */ - Charset getBodyCharset(); - - ConnectionPoolPartitioning getConnectionPoolPartitioning(); - - NameResolver getNameResolver(); -} 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 2a9a0cca58..0000000000 --- a/api/src/main/java/org/asynchttpclient/RequestBuilder.java +++ /dev/null @@ -1,177 +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.proxy.ProxyServer; -import org.asynchttpclient.request.body.multipart.Part; -import org.asynchttpclient.util.UriEncoder; - -/** - * 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 disableUrlEncoding) { - super(RequestBuilder.class, method, disableUrlEncoding); - } - - public RequestBuilder(String method, UriEncoder uriEncoder) { - super(RequestBuilder.class, method, uriEncoder); - } - - public RequestBuilder(Request prototype) { - super(RequestBuilder.class, prototype); - } - - public RequestBuilder(Request prototype, UriEncoder uriEncoder) { - super(RequestBuilder.class, prototype, uriEncoder); - } - - // 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 40eed545c4..0000000000 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ /dev/null @@ -1,666 +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.AsyncHttpProviderUtils.parseCharset; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.validateSupportedScheme; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import java.io.File; -import java.io.InputStream; -import java.net.InetAddress; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.asynchttpclient.channel.NameResolver; -import org.asynchttpclient.channel.pool.ConnectionPoolPartitioning; -import org.asynchttpclient.cookie.Cookie; -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.UriEncoder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Builder for {@link Request} - * - * @param - */ -public abstract class RequestBuilderBase> { - private final static Logger logger = LoggerFactory.getLogger(RequestBuilderBase.class); - - private static final Uri DEFAULT_REQUEST_URL = Uri.create("http://localhost/"); - - private static final class RequestImpl implements Request { - private String method; - private Uri uri; - private InetAddress address; - private InetAddress localAddress; - private FluentCaseInsensitiveStringsMap headers = new FluentCaseInsensitiveStringsMap(); - private ArrayList cookies; - private byte[] byteData; - private List compositeByteData; - private String stringData; - private ByteBuffer byteBufferData; - 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 requestTimeout; - private long rangeOffset; - public Charset charset; - private ConnectionPoolPartitioning connectionPoolPartitioning = ConnectionPoolPartitioning.PerHostConnectionPoolPartitioning.INSTANCE; - private NameResolver nameResolver = NameResolver.JdkNameResolver.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.compositeByteData = prototype.getCompositeByteData(); - this.stringData = prototype.getStringData(); - this.byteBufferData = prototype.getByteBufferData(); - 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.requestTimeout = prototype.getRequestTimeout(); - this.rangeOffset = prototype.getRangeOffset(); - this.charset = prototype.getBodyCharset(); - this.connectionPoolPartitioning = prototype.getConnectionPoolPartitioning(); - this.nameResolver = prototype.getNameResolver(); - } - } - - @Override - public String getUrl() { - return uri.toUrl(); - } - - @Override - public String getMethod() { - return method; - } - - @Override - public InetAddress getInetAddress() { - return address; - } - - @Override - public InetAddress getLocalAddress() { - return localAddress; - } - - @Override - public Uri 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 List getCompositeByteData() { - return compositeByteData; - } - - @Override - public String getStringData() { - return stringData; - } - - @Override - public ByteBuffer getByteBufferData() { - return byteBufferData; - } - - @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 getRequestTimeout() { - return requestTimeout; - } - - @Override - public long getRangeOffset() { - return rangeOffset; - } - - @Override - public Charset getBodyCharset() { - return charset; - } - - @Override - public ConnectionPoolPartitioning getConnectionPoolPartitioning() { - return connectionPoolPartitioning; - } - - @Override - public NameResolver getNameResolver() { - return nameResolver; - } - - @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(getUrl()); - - 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 UriEncoder uriEncoder; - protected List rbQueryParams; - protected SignatureCalculator signatureCalculator; - - protected RequestBuilderBase(Class derived, String method, boolean disableUrlEncoding) { - this(derived, method, UriEncoder.uriEncoder(disableUrlEncoding)); - } - - protected RequestBuilderBase(Class derived, String method, UriEncoder uriEncoder) { - this.derived = derived; - request = new RequestImpl(); - request.method = method; - this.uriEncoder = uriEncoder; - } - - protected RequestBuilderBase(Class derived, Request prototype) { - this(derived, prototype, UriEncoder.FIXING); - } - - protected RequestBuilderBase(Class derived, Request prototype, UriEncoder uriEncoder) { - this.derived = derived; - request = new RequestImpl(prototype); - this.uriEncoder = uriEncoder; - } - - public T setUrl(String url) { - return setUri(Uri.create(url)); - } - - public T setUri(Uri 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.replaceWith(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() { - rbQueryParams = null; - request.uri = request.uri.withNewQuery(null); - } - - public void resetFormParams() { - request.formParams = null; - } - - public void resetNonMultipartData() { - request.byteData = null; - request.compositeByteData = null; - request.byteBufferData = null; - request.stringData = null; - request.streamData = null; - request.bodyGenerator = null; - request.length = -1; - } - - public void resetMultipartData() { - request.parts = null; - } - - public T setBody(File file) { - request.file = file; - return derived.cast(this); - } - - private void resetBody() { - resetFormParams(); - resetNonMultipartData(); - resetMultipartData(); - } - - public T setBody(byte[] data) { - resetBody(); - request.byteData = data; - return derived.cast(this); - } - - public T setBody(List data) { - resetBody(); - request.compositeByteData = data; - return derived.cast(this); - } - - public T setBody(String data) { - resetBody(); - request.stringData = data; - return derived.cast(this); - } - - public T setBody(ByteBuffer data) { - resetBody(); - request.byteBufferData = data; - return derived.cast(this); - } - - public T setBody(InputStream stream) { - resetBody(); - 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 (rbQueryParams == null) - rbQueryParams = new ArrayList<>(1); - rbQueryParams.add(new Param(name, value)); - return derived.cast(this); - } - - public T addQueryParams(List params) { - if (rbQueryParams == null) - rbQueryParams = params; - else - rbQueryParams.addAll(params); - 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) { - // reset existing query - if (isNonEmpty(request.uri.getQuery())) - request.uri = request.uri.withNewQuery(null); - rbQueryParams = 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 setRequestTimeout(int requestTimeout) { - request.requestTimeout = requestTimeout; - 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 setBodyCharset(Charset charset) { - request.charset = charset; - return derived.cast(this); - } - - public T setConnectionPoolPartitioning(ConnectionPoolPartitioning connectionPoolPartitioning) { - request.connectionPoolPartitioning = connectionPoolPartitioning; - return derived.cast(this); - } - - public T setNameResolver(NameResolver nameResolver) { - request.nameResolver = nameResolver; - 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) { - RequestBuilder rb = new RequestBuilder(request).setSignatureCalculator(null); - rb.rbQueryParams = this.rbQueryParams; - Request unsignedRequest = rb.build(); - signatureCalculator.calculateAndAddSignature(unsignedRequest, this); - } - } - - private void computeRequestCharset() { - if (request.charset == null) { - try { - final String contentType = request.headers.getFirstValue("Content-Type"); - if (contentType != null) { - final Charset charset = 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; - } else { - validateSupportedScheme(request.uri); - } - - request.uri = uriEncoder.encode(request.uri, rbQueryParams); - } - - public Request build() { - executeSignatureCalculator(); - computeFinalUri(); - 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 47b6bb772a..0000000000 --- a/api/src/main/java/org/asynchttpclient/Response.java +++ /dev/null @@ -1,222 +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.Uri; - -import java.io.IOException; -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[]. - * @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; - - /** - * 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(Charset charset) 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 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(); - - /** - * 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(); - - /** - * 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(); - - 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/ResponseBase.java b/api/src/main/java/org/asynchttpclient/ResponseBase.java deleted file mode 100644 index e41a2f9eb2..0000000000 --- a/api/src/main/java/org/asynchttpclient/ResponseBase.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.asynchttpclient; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.*; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.uri.Uri; - -import java.net.SocketAddress; -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.List; - -public abstract class ResponseBase implements Response { - - 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 Charset calculateCharset(Charset charset) { - - if (charset == null) { - String contentType = getContentType(); - if (contentType != null) - charset = 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 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(String name) { - return headers != null ? getHeaders().getFirstValue(name) : null; - } - - @Override - public final List getHeaders(String name) { - return headers != null ? getHeaders().get(name) : Collections. emptyList(); - } - - @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/channel/NameResolver.java b/api/src/main/java/org/asynchttpclient/channel/NameResolver.java deleted file mode 100644 index 5835dceac7..0000000000 --- a/api/src/main/java/org/asynchttpclient/channel/NameResolver.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2015 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.channel; - -import java.net.InetAddress; -import java.net.UnknownHostException; - -public interface NameResolver { - - InetAddress resolve(String name) throws UnknownHostException; - - public enum JdkNameResolver implements NameResolver { - - INSTANCE; - - @Override - public InetAddress resolve(String name) throws UnknownHostException { - return InetAddress.getByName(name); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/channel/SSLEngineFactory.java b/api/src/main/java/org/asynchttpclient/channel/SSLEngineFactory.java deleted file mode 100644 index 1e5928c0ce..0000000000 --- a/api/src/main/java/org/asynchttpclient/channel/SSLEngineFactory.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.channel; - -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import java.security.GeneralSecurityException; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLParameters; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.util.SslUtils; - -/** - * 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(String peerHost, int peerPort) throws GeneralSecurityException; - - public static class DefaultSSLEngineFactory implements SSLEngineFactory { - - private final AsyncHttpClientConfig config; - - public DefaultSSLEngineFactory(AsyncHttpClientConfig config) { - this.config = config; - } - - @Override - public SSLEngine newSSLEngine(String peerHost, int peerPort) throws GeneralSecurityException { - SSLContext sslContext = SslUtils.getInstance().getSSLContext(config); - - SSLEngine sslEngine = sslContext.createSSLEngine(peerHost, peerPort); - if (!config.isAcceptAnyCertificate()) { - SSLParameters params = sslEngine.getSSLParameters(); - params.setEndpointIdentificationAlgorithm("HTTPS"); - sslEngine.setSSLParameters(params); - } - sslEngine.setUseClientMode(true); - - if (isNonEmpty(config.getEnabledProtocols())) - sslEngine.setEnabledProtocols(config.getEnabledProtocols()); - - if (isNonEmpty(config.getEnabledCipherSuites())) - sslEngine.setEnabledCipherSuites(config.getEnabledCipherSuites()); - - return sslEngine; - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/channel/pool/ConnectionPoolPartitioning.java b/api/src/main/java/org/asynchttpclient/channel/pool/ConnectionPoolPartitioning.java deleted file mode 100644 index 333d2e50e8..0000000000 --- a/api/src/main/java/org/asynchttpclient/channel/pool/ConnectionPoolPartitioning.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.channel.pool; - -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.uri.Uri; -import org.asynchttpclient.util.AsyncHttpProviderUtils; - -public interface ConnectionPoolPartitioning { - - public class ProxyPartitionKey { - private final String proxyUrl; - private final String targetHostBaseUrl; - - public ProxyPartitionKey(String proxyUrl, String targetHostBaseUrl) { - this.proxyUrl = proxyUrl; - this.targetHostBaseUrl = targetHostBaseUrl; - } - - @Override - public String toString() { - return new StringBuilder()// - .append("ProxyPartitionKey(proxyUrl=").append(proxyUrl)// - .append(", targetHostBaseUrl=").append(targetHostBaseUrl)// - .toString(); - } - } - - Object getPartitionKey(Uri uri, String virtualHost, ProxyServer proxyServer); - - public enum PerHostConnectionPoolPartitioning implements ConnectionPoolPartitioning { - - INSTANCE; - - public Object getPartitionKey(Uri uri, String virtualHost, ProxyServer proxyServer) { - String targetHostBaseUrl = virtualHost != null ? virtualHost : AsyncHttpProviderUtils.getBaseUrl(uri); - return proxyServer != null ? new ProxyPartitionKey(proxyServer.getUrl(), targetHostBaseUrl) : targetHostBaseUrl; - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/channel/pool/ConnectionStrategy.java b/api/src/main/java/org/asynchttpclient/channel/pool/ConnectionStrategy.java deleted file mode 100644 index 5135ca6c83..0000000000 --- a/api/src/main/java/org/asynchttpclient/channel/pool/ConnectionStrategy.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.channel.pool; - -/** - * Provides an interface for decisions about HTTP connections. - */ -public interface ConnectionStrategy { - - /** - * Determines whether the connection should be kept alive after this HTTP message exchange. - * @param request the HTTP request - * @param response the HTTP response - * @return true if the connection should be kept alive, false if it should be closed. - */ - boolean keepAlive(REQUEST request, RESPONSE response); -} diff --git a/api/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigBean.java b/api/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigBean.java deleted file mode 100644 index cd5b680d9f..0000000000 --- a/api/src/main/java/org/asynchttpclient/config/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.config; - -import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.*; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.Realm; -import org.asynchttpclient.filter.IOExceptionFilter; -import org.asynchttpclient.filter.RequestFilter; -import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.proxy.ProxyServerSelector; -import org.asynchttpclient.util.ProxyUtils; - -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(); - connectTimeout = defaultConnectTimeout(); - webSocketTimeout = defaultWebSocketTimeout(); - pooledConnectionIdleTimeout = defaultPooledConnectionIdleTimeout(); - readTimeout = defaultReadTimeout(); - requestTimeout = defaultRequestTimeout(); - connectionTTL = defaultConnectionTTL(); - followRedirect = defaultFollowRedirect(); - maxRedirects = defaultMaxRedirects(); - compressionEnforced = defaultCompressionEnforced(); - userAgent = defaultUserAgent(); - allowPoolingConnections = defaultAllowPoolingConnections(); - maxRequestRetry = defaultMaxRequestRetry(); - ioThreadMultiplier = defaultIoThreadMultiplier(); - allowPoolingSslConnections = defaultAllowPoolingSslConnections(); - disableUrlEncodingForBoundRequests = defaultDisableUrlEncodingForBoundRequests(); - strict302Handling = defaultStrict302Handling(); - acceptAnyCertificate = defaultAcceptAnyCertificate(); - sslSessionCacheSize = defaultSslSessionCacheSize(); - sslSessionTimeout = defaultSslSessionTimeout(); - - if (defaultUseProxySelector()) { - proxyServerSelector = ProxyUtils.getJdkDefaultProxyServerSelector(); - } else if (defaultUseProxyProperties()) { - proxyServerSelector = ProxyUtils.createProxyServerSelector(System.getProperties()); - } - } - - 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 setConnectTimeout(int connectTimeout) { - this.connectTimeout = connectTimeout; - 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 setCompressionEnforced(boolean compressionEnforced) { - this.compressionEnforced = compressionEnforced; - 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 setIoThreadMultiplier(int ioThreadMultiplier) { - this.ioThreadMultiplier = ioThreadMultiplier; - return this; - } - - public AsyncHttpClientConfigBean setAcceptAnyCertificate(boolean acceptAnyCertificate) { - this.acceptAnyCertificate = acceptAnyCertificate; - return this; - } - - public AsyncHttpClientConfigBean setSslSessionCacheSize(Integer sslSessionCacheSize) { - this.sslSessionCacheSize = sslSessionCacheSize; - return this; - } - - public AsyncHttpClientConfigBean setSslSessionTimeout(Integer sslSessionTimeout) { - this.sslSessionTimeout = sslSessionTimeout; - return this; - } -} diff --git a/api/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java deleted file mode 100644 index 0f879e7341..0000000000 --- a/api/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java +++ /dev/null @@ -1,153 +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.config; - -public final class AsyncHttpClientConfigDefaults { - - private AsyncHttpClientConfigDefaults() { - } - - public static final String ASYNC_CLIENT_CONFIG_ROOT = "org.asynchttpclient."; - - public static int defaultMaxConnections() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "maxConnections"); - } - - public static int defaultMaxConnectionsPerHost() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "maxConnectionsPerHost"); - } - - public static int defaultConnectTimeout() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "connectTimeout"); - } - - public static int defaultPooledConnectionIdleTimeout() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "pooledConnectionIdleTimeout"); - } - - public static int defaultReadTimeout() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "readTimeout"); - } - - public static int defaultRequestTimeout() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "requestTimeout"); - } - - public static int defaultWebSocketTimeout() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "webSocketTimeout"); - } - - public static int defaultConnectionTTL() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "connectionTTL"); - } - - public static boolean defaultFollowRedirect() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + "followRedirect"); - } - - public static int defaultMaxRedirects() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "maxRedirects"); - } - - public static boolean defaultCompressionEnforced() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + "compressionEnforced"); - } - - public static String defaultUserAgent() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getString(ASYNC_CLIENT_CONFIG_ROOT + "userAgent"); - } - - public static int defaultIoThreadMultiplier() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "ioThreadMultiplier"); - } - - public static String[] defaultEnabledProtocols() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getStringArray(ASYNC_CLIENT_CONFIG_ROOT + "enabledProtocols"); - } - - public static boolean defaultUseProxySelector() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + "useProxySelector"); - } - - public static boolean defaultUseProxyProperties() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + "useProxyProperties"); - } - - public static boolean defaultStrict302Handling() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + "strict302Handling"); - } - - public static boolean defaultAllowPoolingConnections() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + "allowPoolingConnections"); - } - - public static int defaultMaxRequestRetry() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "maxRequestRetry"); - } - - public static boolean defaultAllowPoolingSslConnections() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + "allowPoolingSslConnections"); - } - - public static boolean defaultDisableUrlEncodingForBoundRequests() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + "disableUrlEncodingForBoundRequests"); - } - - public static boolean defaultAcceptAnyCertificate() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + "acceptAnyCertificate"); - } - - public static Integer defaultSslSessionCacheSize() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInteger(ASYNC_CLIENT_CONFIG_ROOT + "sslSessionCacheSize"); - } - - public static Integer defaultSslSessionTimeout() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInteger(ASYNC_CLIENT_CONFIG_ROOT + "sslSessionTimeout"); - } - - public static int defaultHttpClientCodecMaxInitialLineLength() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "httpClientCodecMaxInitialLineLength"); - } - - public static int defaultHttpClientCodecMaxHeaderSize() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "httpClientCodecMaxHeaderSize"); - } - - public static int defaultHttpClientCodecMaxChunkSize() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "httpClientCodecMaxChunkSize"); - } - - public static boolean defaultDisableZeroCopy() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + "disableZeroCopy"); - } - - public static long defaultHandshakeTimeout() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getLong(ASYNC_CLIENT_CONFIG_ROOT + "handshakeTimeout"); - } - - public static int defaultChunkedFileChunkSize() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "chunkedFileChunkSize"); - } - - public static int defaultWebSocketMaxBufferSize() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "webSocketMaxBufferSize"); - } - - public static int defaultWebSocketMaxFrameSize() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + "webSocketMaxFrameSize"); - } - - public static boolean defaultKeepEncodingHeader() { - return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + "keepEncodingHeader"); - } -} diff --git a/api/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigHelper.java b/api/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigHelper.java deleted file mode 100644 index 43bbe966ca..0000000000 --- a/api/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigHelper.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.asynchttpclient.config; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - -import org.asynchttpclient.internal.chmv8.ConcurrentHashMapV8; - -public class AsyncHttpClientConfigHelper { - - private static volatile Config config; - - 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() { - if (config != null) - config.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 ConcurrentHashMapV8 propsCache = new ConcurrentHashMapV8(); - private final Properties defaultProperties = parsePropertiesFile(DEFAULT_AHC_PROPERTIES); - private volatile Properties customProperties = parsePropertiesFile(CUSTOM_AHC_PROPERTIES); - - public void reload() { - customProperties = parsePropertiesFile(CUSTOM_AHC_PROPERTIES); - propsCache.clear(); - } - - private Properties parsePropertiesFile(String file) { - Properties props = new Properties(); - try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(file)) { - if (is != null) { - props.load(is); - } - } catch (IOException e) { - throw new IllegalArgumentException("Can't parse file", e); - } - return props; - } - - public String getString(String key) { - return propsCache.computeIfAbsent(key, new ConcurrentHashMapV8.Fun() { - - @Override - public String apply(String key) { - String value = System.getProperty(key); - if (value == null) { - value = (String) customProperties.getProperty(key); - } - if (value == null) { - value = (String) defaultProperties.getProperty(key); - } - - return value; - } - }); - } - - public String[] getStringArray(String key) { - String s = getString(key); - 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 long getLong(String key) { - return Long.parseLong(getString(key)); - } - - public Integer getInteger(String key) { - String s = getString(key); - return s != null ? Integer.valueOf(s) : null; - } - - public boolean getBoolean(String key) { - return Boolean.parseBoolean(getString(key)); - } - } -} 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 83d2e0ac45..0000000000 --- a/api/src/main/java/org/asynchttpclient/cookie/Cookie.java +++ /dev/null @@ -1,167 +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, boolean wrap, String domain, String path, long maxAge, boolean secure, boolean httpOnly) { - - if (name == null) { - throw new NullPointerException("name"); - } - name = name.trim(); - if (name.length() == 0) { - 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, wrap, domain, path, maxAge, secure, httpOnly); - } - - private static String validateValue(String name, String value) { - if (value == null) { - return null; - } - value = value.trim(); - if (value.length() == 0) { - 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 boolean wrap; - private final String domain; - private final String path; - private final long maxAge; - private final boolean secure; - private final boolean httpOnly; - - public Cookie(String name, String value, boolean wrap, String domain, String path, long maxAge, boolean secure, boolean httpOnly) { - this.name = name; - this.value = value; - this.wrap = wrap; - this.domain = domain; - this.path = path; - 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 boolean isWrap() { - return wrap; - } - - public String getPath() { - return path; - } - - public long 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('='); - if (wrap) - buf.append('"').append(value).append('"'); - else - buf.append(value); - if (domain != null) { - buf.append("; domain="); - buf.append(domain); - } - if (path != null) { - buf.append("; path="); - buf.append(path); - } - 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 24695595c4..0000000000 --- a/api/src/main/java/org/asynchttpclient/cookie/CookieDecoder.java +++ /dev/null @@ -1,279 +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.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.CharBuffer; - -import static org.asynchttpclient.cookie.CookieUtil.*; - -public class CookieDecoder { - - private static final Logger LOGGER = LoggerFactory.getLogger(CookieDecoder.class); - - /** - * Decodes the specified HTTP header value into {@link Cookie}. - * - * @return the decoded {@link Cookie} - */ - public static Cookie decode(String header) { - - if (header == null) { - throw new NullPointerException("header"); - } - - final int headerLen = header.length(); - - if (headerLen == 0) { - return null; - } - - CookieBuilder cookieBuilder = null; - - 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 nameBegin = i; - int nameEnd = i; - int valueStart = -1; - int valueEnd = -1; - - if (i != headerLen) { - keyValLoop: for (;;) { - - char curChar = header.charAt(i); - if (curChar == ';') { - // NAME; (no value till ';') - nameEnd = i; - valueStart = valueEnd = -1; - break keyValLoop; - - } else if (curChar == '=') { - // NAME=VALUE - nameEnd = i; - i++; - if (i == headerLen) { - // NAME= (empty value, i.e. nothing after '=') - valueStart = valueEnd = 0; - break keyValLoop; - } - - valueStart = i; - // NAME=VALUE; - int semiPos = header.indexOf(';', i); - valueEnd = i = semiPos > 0 ? semiPos : headerLen; - break keyValLoop; - } else { - i++; - } - - if (i == headerLen) { - // NAME (no value till the end of string) - nameEnd = headerLen; - valueStart = valueEnd = -1; - break; - } - } - } - - if (valueEnd > 0 && header.charAt(valueEnd - 1) == ',') { - // old multiple cookies separator, skipping it - valueEnd--; - } - - if (cookieBuilder == null) { - // cookie name-value pair - if (nameBegin == -1 || nameBegin == nameEnd) { - LOGGER.debug("Skipping cookie with null name"); - return null; - } - - if (valueStart == -1) { - LOGGER.debug("Skipping cookie with null value"); - return null; - } - - CharSequence wrappedValue = CharBuffer.wrap(header, valueStart, valueEnd); - CharSequence unwrappedValue = unwrapValue(wrappedValue); - if (unwrappedValue == null) { - LOGGER.debug("Skipping cookie because starting quotes are not properly balanced in '{}'", unwrappedValue); - return null; - } - - final String name = header.substring(nameBegin, nameEnd); - - final boolean wrap = unwrappedValue.length() != valueEnd - valueStart; - - cookieBuilder = new CookieBuilder(name, unwrappedValue.toString(), wrap, header); - - } else { - // cookie attribute - cookieBuilder.appendAttribute(nameBegin, nameEnd, valueStart, valueEnd); - } - } - return cookieBuilder.cookie(); - } - - private static class CookieBuilder { - - private static final String PATH = "Path"; - - private static final String EXPIRES = "Expires"; - - private static final String MAX_AGE = "Max-Age"; - - private static final String DOMAIN = "Domain"; - - private static final String SECURE = "Secure"; - - private static final String HTTPONLY = "HTTPOnly"; - - private final String name; - private final String value; - private final boolean wrap; - private final String header; - private String domain; - private String path; - private long maxAge = Long.MIN_VALUE; - private int expiresStart; - private int expiresEnd; - private boolean secure; - private boolean httpOnly; - - public CookieBuilder(String name, String value, boolean wrap, String header) { - this.name = name; - this.value = value; - this.wrap = wrap; - this.header = header; - } - - public Cookie cookie() { - return new Cookie(name, value, wrap, domain, path, mergeMaxAgeAndExpires(), secure, httpOnly); - } - - private long mergeMaxAgeAndExpires() { - // max age has precedence over expires - if (maxAge != Long.MIN_VALUE) { - return maxAge; - } else { - String expires = computeValue(expiresStart, expiresEnd); - if (expires != null) { - long expiresMillis = computeExpires(expires); - if (expiresMillis != Long.MIN_VALUE) { - long maxAgeMillis = expiresMillis - System.currentTimeMillis(); - return maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0 ? 1 : 0); - } - } - } - return Long.MIN_VALUE; - } - - /** - * Parse and store a key-value pair. First one is considered to be the - * cookie name/value. Unknown attribute names are silently discarded. - * - * @param keyStart - * where the key starts in the header - * @param keyEnd - * where the key ends in the header - * @param valueStart - * where the value starts in the header - * @param valueEnd - * where the value ends in the header - */ - public void appendAttribute(int keyStart, int keyEnd, int valueStart, int valueEnd) { - setCookieAttribute(keyStart, keyEnd, valueStart, valueEnd); - } - - private void setCookieAttribute(int keyStart, int keyEnd, int valueStart, int valueEnd) { - - int length = keyEnd - keyStart; - - if (length == 4) { - parse4(keyStart, valueStart, valueEnd); - } else if (length == 6) { - parse6(keyStart, valueStart, valueEnd); - } else if (length == 7) { - parse7(keyStart, valueStart, valueEnd); - } else if (length == 8) { - parse8(keyStart, valueStart, valueEnd); - } - } - - private void parse4(int nameStart, int valueStart, int valueEnd) { - if (header.regionMatches(true, nameStart, PATH, 0, 4)) { - path = computeValue(valueStart, valueEnd); - } - } - - private void parse6(int nameStart, int valueStart, int valueEnd) { - if (header.regionMatches(true, nameStart, DOMAIN, 0, 5)) { - domain = computeValue(valueStart, valueEnd); - } else if (header.regionMatches(true, nameStart, SECURE, 0, 5)) { - secure = true; - } - } - - private void parse7(int nameStart, int valueStart, int valueEnd) { - if (header.regionMatches(true, nameStart, EXPIRES, 0, 7)) { - expiresStart = valueStart; - expiresEnd = valueEnd; - } else if (header.regionMatches(true, nameStart, MAX_AGE, 0, 7)) { - try { - maxAge = Math.max(Integer.valueOf(computeValue(valueStart, valueEnd)), 0); - } catch (NumberFormatException e1) { - // ignore failure to parse -> treat as session cookie - } - } - } - - private void parse8(int nameStart, int valueStart, int valueEnd) { - if (header.regionMatches(true, nameStart, HTTPONLY, 0, 8)) { - httpOnly = true; - } - } - - private String computeValue(int valueStart, int valueEnd) { - if (valueStart == -1 || valueStart == valueEnd) { - return null; - } else { - while (valueStart < valueEnd && header.charAt(valueStart) <= ' ') { - valueStart++; - } - while (valueStart < valueEnd && (header.charAt(valueEnd - 1) <= ' ')) { - valueEnd--; - } - return valueStart == valueEnd ? null : header.substring(valueStart, valueEnd); - } - } - } -} 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 ab657edf15..0000000000 --- a/api/src/main/java/org/asynchttpclient/cookie/CookieEncoder.java +++ /dev/null @@ -1,52 +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.util.StringUtils; - -import java.util.Collection; - -public final class CookieEncoder { - - private CookieEncoder() { - } - - public static String encode(Collection cookies) { - StringBuilder sb = StringUtils.stringBuilder(); - - for (Cookie cookie : cookies) { - add(sb, cookie.getName(), cookie.getValue(), cookie.isWrap()); - } - - if (sb.length() > 0) { - sb.setLength(sb.length() - 2); - } - return sb.toString(); - } - - private static void add(StringBuilder sb, String name, String val, boolean wrap) { - - if (val == null) { - val = ""; - } - - sb.append(name); - sb.append('='); - if (wrap) - sb.append('"').append(val).append('"'); - else - sb.append(val); - sb.append(';'); - sb.append(' '); - } -} diff --git a/api/src/main/java/org/asynchttpclient/cookie/CookieUtil.java b/api/src/main/java/org/asynchttpclient/cookie/CookieUtil.java deleted file mode 100644 index c1cad3817c..0000000000 --- a/api/src/main/java/org/asynchttpclient/cookie/CookieUtil.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2015 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.text.ParsePosition; -import java.util.BitSet; -import java.util.Date; - -public class CookieUtil { - - private static final BitSet VALID_COOKIE_VALUE_OCTETS = validCookieValueOctets(); - - private static final BitSet VALID_COOKIE_NAME_OCTETS = validCookieNameOctets(VALID_COOKIE_VALUE_OCTETS); - - // US-ASCII characters excluding CTLs, whitespace, DQUOTE, comma, semicolon, and backslash - private static BitSet validCookieValueOctets() { - - BitSet bits = new BitSet(8); - for (int i = 35; i < 127; i++) { - // US-ASCII characters excluding CTLs (%x00-1F / %x7F) - bits.set(i); - } - bits.set('"', false); // exclude DQUOTE = %x22 - bits.set(',', false); // exclude comma = %x2C - bits.set(';', false); // exclude semicolon = %x3B - bits.set('\\', false); // exclude backslash = %x5C - return bits; - } - - // token = 1* - // separators = "(" | ")" | "<" | ">" | "@" - // | "," | ";" | ":" | "\" | <"> - // | "/" | "[" | "]" | "?" | "=" - // | "{" | "}" | SP | HT - private static BitSet validCookieNameOctets(BitSet validCookieValueOctets) { - BitSet bits = new BitSet(8); - bits.or(validCookieValueOctets); - bits.set('(', false); - bits.set(')', false); - bits.set('<', false); - bits.set('>', false); - bits.set('@', false); - bits.set(':', false); - bits.set('/', false); - bits.set('[', false); - bits.set(']', false); - bits.set('?', false); - bits.set('=', false); - bits.set('{', false); - bits.set('}', false); - bits.set(' ', false); - bits.set('\t', false); - return bits; - } - - static int firstInvalidCookieNameOctet(CharSequence cs) { - return firstInvalidOctet(cs, VALID_COOKIE_NAME_OCTETS); - } - - static int firstInvalidCookieValueOctet(CharSequence cs) { - return firstInvalidOctet(cs, VALID_COOKIE_VALUE_OCTETS); - } - - static int firstInvalidOctet(CharSequence cs, BitSet bits) { - - for (int i = 0; i < cs.length(); i++) { - char c = cs.charAt(i); - if (!bits.get(c)) { - return i; - } - } - return -1; - } - - static CharSequence unwrapValue(CharSequence cs) { - final int len = cs.length(); - if (len > 0 && cs.charAt(0) == '"') { - if (len >= 2 && cs.charAt(len - 1) == '"') { - // properly balanced - return len == 2 ? "" : cs.subSequence(1, len - 1); - } else { - return null; - } - } - return cs; - } - - static long computeExpires(String expires) { - if (expires != null) { - Date expiresDate = RFC2616DateParser.get().parse(expires, new ParsePosition(0)); - if (expiresDate != null) - return expiresDate.getTime(); - } - - return Long.MIN_VALUE; - } - - private CookieUtil() { - // Unused - } -} diff --git a/api/src/main/java/org/asynchttpclient/cookie/RFC2616DateParser.java b/api/src/main/java/org/asynchttpclient/cookie/RFC2616DateParser.java deleted file mode 100644 index 5e39fd6d6e..0000000000 --- a/api/src/main/java/org/asynchttpclient/cookie/RFC2616DateParser.java +++ /dev/null @@ -1,91 +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.text.ParsePosition; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; - -/** - * A parser for RFC2616 - * Date format. - * - * @author slandelle - */ -@SuppressWarnings("serial") -public class RFC2616DateParser extends SimpleDateFormat { - - private final SimpleDateFormat format1 = new RFC2616DateParserObsolete1(); - private final SimpleDateFormat format2 = new RFC2616DateParserObsolete2(); - - private static final ThreadLocal DATE_FORMAT_HOLDER = new ThreadLocal() { - @Override - protected RFC2616DateParser initialValue() { - return new RFC2616DateParser(); - } - }; - - public static RFC2616DateParser get() { - return DATE_FORMAT_HOLDER.get(); - } - - /** - * Standard date format

- * Sun, 06 Nov 1994 08:49:37 GMT -> E, d MMM yyyy HH:mm:ss z - */ - private RFC2616DateParser() { - super("E, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH); - setTimeZone(TimeZone.getTimeZone("GMT")); - } - - @Override - public Date parse(String text, ParsePosition pos) { - Date date = super.parse(text, pos); - if (date == null) { - date = format1.parse(text, pos); - } - if (date == null) { - date = format2.parse(text, pos); - } - return date; - } - - /** - * First obsolete format

- * Sunday, 06-Nov-94 08:49:37 GMT -> E, d-MMM-y HH:mm:ss z - */ - private static final class RFC2616DateParserObsolete1 extends SimpleDateFormat { - private static final long serialVersionUID = -3178072504225114298L; - - RFC2616DateParserObsolete1() { - super("E, dd-MMM-yy HH:mm:ss z", Locale.ENGLISH); - setTimeZone(TimeZone.getTimeZone("GMT")); - } - } - - /** - * Second obsolete format - *

- * Sun Nov 6 08:49:37 1994 -> EEE, MMM d HH:mm:ss yyyy - */ - private static final class RFC2616DateParserObsolete2 extends SimpleDateFormat { - private static final long serialVersionUID = 3010674519968303714L; - - RFC2616DateParserObsolete2() { - super("E MMM d HH:mm:ss yyyy", Locale.ENGLISH); - setTimeZone(TimeZone.getTimeZone("GMT")); - } - } -} 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 7738990dff..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 198fbbe612..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 286b19bd77..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/future/AbstractListenableFuture.java b/api/src/main/java/org/asynchttpclient/future/AbstractListenableFuture.java deleted file mode 100644 index 0cf69728db..0000000000 --- a/api/src/main/java/org/asynchttpclient/future/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.future; - -import java.util.concurrent.Executor; - -import org.asynchttpclient.ListenableFuture; - -/** - *

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/future/ExecutionList.java b/api/src/main/java/org/asynchttpclient/future/ExecutionList.java deleted file mode 100644 index 50364f7bdd..0000000000 --- a/api/src/main/java/org/asynchttpclient/future/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.future; - -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/handler/AsyncHandlerExtensions.java b/api/src/main/java/org/asynchttpclient/handler/AsyncHandlerExtensions.java deleted file mode 100644 index 67bcdb1dc7..0000000000 --- a/api/src/main/java/org/asynchttpclient/handler/AsyncHandlerExtensions.java +++ /dev/null @@ -1,85 +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.handler; - -import java.net.InetAddress; - -import org.asynchttpclient.AsyncHandler; - -/** - * 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. - * - */ -public interface AsyncHandlerExtensions { - - /** - * Notify the callback when trying to open a new connection. - */ - void onConnectionOpen(); - - /** - * Notify the callback when a new connection was successfully opened. - * - * @param connection the connection - */ - void onConnectionOpened(Object connection); - - /** - * Notify the callback when trying to fetch a connection from the pool. - */ - void onConnectionPool(); - - /** - * Notify the callback when a new connection was successfully fetched from - * the pool. - * - * @param connection the connection - */ - void onConnectionPooled(Object connection); - - /** - * Notify the callback when trying to offer a connection to the pool. - * - * @param connection the connection - */ - void onConnectionOffer(Object connection); - - /** - * 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. - * - * @param request the real request object as passed to the provider - */ - void onRequestSend(Object request); - - /** - * Notify the callback every time a request is being retried. - */ - void onRetry(); - - /** - * Notify the callback after DNS resolution has completed. - * - * @param address the resolved address - */ - void onDnsResolved(InetAddress address); - - /** - * Notify the callback when the SSL handshake performed to establish an - * HTTPS connection has been completed. - */ - void onSslHandshakeCompleted(); -} diff --git a/api/src/main/java/org/asynchttpclient/handler/BodyDeferringAsyncHandler.java b/api/src/main/java/org/asynchttpclient/handler/BodyDeferringAsyncHandler.java deleted file mode 100644 index b5d252c381..0000000000 --- a/api/src/main/java/org/asynchttpclient/handler/BodyDeferringAsyncHandler.java +++ /dev/null @@ -1,284 +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.handler; - -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; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; - -/** - * 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/handler/MaxRedirectException.java b/api/src/main/java/org/asynchttpclient/handler/MaxRedirectException.java deleted file mode 100644 index e6ca7a51a5..0000000000 --- a/api/src/main/java/org/asynchttpclient/handler/MaxRedirectException.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.handler; - -import org.asynchttpclient.AsyncHttpClientConfig; - -/** - * Thrown when the {@link AsyncHttpClientConfig#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/api/src/main/java/org/asynchttpclient/handler/TransferCompletionHandler.java b/api/src/main/java/org/asynchttpclient/handler/TransferCompletionHandler.java deleted file mode 100644 index e95000e9a3..0000000000 --- a/api/src/main/java/org/asynchttpclient/handler/TransferCompletionHandler.java +++ /dev/null @@ -1,241 +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.handler; - -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; - // Netty 3 bug hack: last chunk is not notified, fixed in Netty 4 - private boolean patchForNetty3; - private long expectedTotal; - private long seen; - - /** - * 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; - } - - public void patchForNetty3() { - this.patchForNetty3 = true; - } - - /** - * 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; - if (patchForNetty3) { - String contentLength = headers.getFirstValue("Content-Length"); - if (contentLength != null) - expectedTotal = Long.valueOf(contentLength); - } - } - - @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 { - if (patchForNetty3) { - // some chunks weren't notified, probably the last one - if (seen < expectedTotal) { - // do once - fireOnBytesSent(expectedTotal - seen, expectedTotal, expectedTotal); - } - } - 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) { - if (patchForNetty3) { - seen += amount; - } - 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/handler/resumable/ResumableIOExceptionFilter.java b/api/src/main/java/org/asynchttpclient/handler/resumable/ResumableIOExceptionFilter.java deleted file mode 100644 index c87867499b..0000000000 --- a/api/src/main/java/org/asynchttpclient/handler/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.handler.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/internal/chmv8/ConcurrentHashMapV8.java b/api/src/main/java/org/asynchttpclient/internal/chmv8/ConcurrentHashMapV8.java deleted file mode 100644 index f27225f117..0000000000 --- a/api/src/main/java/org/asynchttpclient/internal/chmv8/ConcurrentHashMapV8.java +++ /dev/null @@ -1,6207 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project 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. - */ - -/* - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -package org.asynchttpclient.internal.chmv8; - -import java.io.ObjectStreamField; -import java.io.Serializable; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.Collection; -import java.util.ConcurrentModificationException; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.LockSupport; -import java.util.concurrent.locks.ReentrantLock; - -/** - * A hash table supporting full concurrency of retrievals and - * high expected concurrency for updates. This class obeys the - * same functional specification as {@link java.util.Hashtable}, and - * includes versions of methods corresponding to each method of - * {@code Hashtable}. However, even though all operations are - * thread-safe, retrieval operations do not entail locking, - * and there is not any support for locking the entire table - * in a way that prevents all access. This class is fully - * interoperable with {@code Hashtable} in programs that rely on its - * thread safety but not on its synchronization details. - * - *

Retrieval operations (including {@code get}) generally do not - * block, so may overlap with update operations (including {@code put} - * and {@code remove}). Retrievals reflect the results of the most - * recently completed update operations holding upon their - * onset. (More formally, an update operation for a given key bears a - * happens-before relation with any (non-null) retrieval for - * that key reporting the updated value.) For aggregate operations - * such as {@code putAll} and {@code clear}, concurrent retrievals may - * reflect insertion or removal of only some entries. Similarly, - * Iterators and Enumerations return elements reflecting the state of - * the hash table at some point at or since the creation of the - * iterator/enumeration. They do not throw {@link - * ConcurrentModificationException}. However, iterators are designed - * to be used by only one thread at a time. Bear in mind that the - * results of aggregate status methods including {@code size}, {@code - * isEmpty}, and {@code containsValue} are typically useful only when - * a map is not undergoing concurrent updates in other threads. - * Otherwise the results of these methods reflect transient states - * that may be adequate for monitoring or estimation purposes, but not - * for program control. - * - *

The table is dynamically expanded when there are too many - * collisions (i.e., keys that have distinct hash codes but fall into - * the same slot modulo the table size), with the expected average - * effect of maintaining roughly two bins per mapping (corresponding - * to a 0.75 load factor threshold for resizing). There may be much - * variance around this average as mappings are added and removed, but - * overall, this maintains a commonly accepted time/space tradeoff for - * hash tables. However, resizing this or any other kind of hash - * table may be a relatively slow operation. When possible, it is a - * good idea to provide a size estimate as an optional {@code - * initialCapacity} constructor argument. An additional optional - * {@code loadFactor} constructor argument provides a further means of - * customizing initial table capacity by specifying the table density - * to be used in calculating the amount of space to allocate for the - * given number of elements. Also, for compatibility with previous - * versions of this class, constructors may optionally specify an - * expected {@code concurrencyLevel} as an additional hint for - * internal sizing. Note that using many keys with exactly the same - * {@code hashCode()} is a sure way to slow down performance of any - * hash table. To ameliorate impact, when keys are {@link Comparable}, - * this class may use comparison order among keys to help break ties. - * - *

A {@link Set} projection of a ConcurrentHashMapV8 may be created - * (using {@link #newKeySet()} or {@link #newKeySet(int)}), or viewed - * (using {@link #keySet(Object)} when only keys are of interest, and the - * mapped values are (perhaps transiently) not used or all take the - * same mapping value. - * - *

This class and its views and iterators implement all of the - * optional methods of the {@link Map} and {@link Iterator} - * interfaces. - * - *

Like {@link Hashtable} but unlike {@link HashMap}, this class - * does not allow {@code null} to be used as a key or value. - * - *

ConcurrentHashMapV8s support a set of sequential and parallel bulk - * operations that are designed - * to be safely, and often sensibly, applied even with maps that are - * being concurrently updated by other threads; for example, when - * computing a snapshot summary of the values in a shared registry. - * There are three kinds of operation, each with four forms, accepting - * functions with Keys, Values, Entries, and (Key, Value) arguments - * and/or return values. Because the elements of a ConcurrentHashMapV8 - * are not ordered in any particular way, and may be processed in - * different orders in different parallel executions, the correctness - * of supplied functions should not depend on any ordering, or on any - * other objects or values that may transiently change while - * computation is in progress; and except for forEach actions, should - * ideally be side-effect-free. Bulk operations on {@link java.util.Map.Entry} - * objects do not support method {@code setValue}. - * - *

    - *
  • forEach: Perform a given action on each element. - * A variant form applies a given transformation on each element - * before performing the action.
  • - * - *
  • search: Return the first available non-null result of - * applying a given function on each element; skipping further - * search when a result is found.
  • - * - *
  • reduce: Accumulate each element. The supplied reduction - * function cannot rely on ordering (more formally, it should be - * both associative and commutative). There are five variants: - * - *
      - * - *
    • Plain reductions. (There is not a form of this method for - * (key, value) function arguments since there is no corresponding - * return type.)
    • - * - *
    • Mapped reductions that accumulate the results of a given - * function applied to each element.
    • - * - *
    • Reductions to scalar doubles, longs, and ints, using a - * given basis value.
    • - * - *
    - *
  • - *
- * - *

These bulk operations accept a {@code parallelismThreshold} - * argument. Methods proceed sequentially if the current map size is - * estimated to be less than the given threshold. Using a value of - * {@code Long.MAX_VALUE} suppresses all parallelism. Using a value - * of {@code 1} results in maximal parallelism by partitioning into - * enough subtasks to fully utilize the {@link - * ForkJoinPool#commonPool()} that is used for all parallel - * computations. Normally, you would initially choose one of these - * extreme values, and then measure performance of using in-between - * values that trade off overhead versus throughput. - * - *

The concurrency properties of bulk operations follow - * from those of ConcurrentHashMapV8: Any non-null result returned - * from {@code get(key)} and related access methods bears a - * happens-before relation with the associated insertion or - * update. The result of any bulk operation reflects the - * composition of these per-element relations (but is not - * necessarily atomic with respect to the map as a whole unless it - * is somehow known to be quiescent). Conversely, because keys - * and values in the map are never null, null serves as a reliable - * atomic indicator of the current lack of any result. To - * maintain this property, null serves as an implicit basis for - * all non-scalar reduction operations. For the double, long, and - * int versions, the basis should be one that, when combined with - * any other value, returns that other value (more formally, it - * should be the identity element for the reduction). Most common - * reductions have these properties; for example, computing a sum - * with basis 0 or a minimum with basis MAX_VALUE. - * - *

Search and transformation functions provided as arguments - * should similarly return null to indicate the lack of any result - * (in which case it is not used). In the case of mapped - * reductions, this also enables transformations to serve as - * filters, returning null (or, in the case of primitive - * specializations, the identity basis) if the element should not - * be combined. You can create compound transformations and - * filterings by composing them yourself under this "null means - * there is nothing there now" rule before using them in search or - * reduce operations. - * - *

Methods accepting and/or returning Entry arguments maintain - * key-value associations. They may be useful for example when - * finding the key for the greatest value. Note that "plain" Entry - * arguments can be supplied using {@code new - * AbstractMap.SimpleEntry(k,v)}. - * - *

Bulk operations may complete abruptly, throwing an - * exception encountered in the application of a supplied - * function. Bear in mind when handling such exceptions that other - * concurrently executing functions could also have thrown - * exceptions, or would have done so if the first exception had - * not occurred. - * - *

Speedups for parallel compared to sequential forms are common - * but not guaranteed. Parallel operations involving brief functions - * on small maps may execute more slowly than sequential forms if the - * underlying work to parallelize the computation is more expensive - * than the computation itself. Similarly, parallelization may not - * lead to much actual parallelism if all processors are busy - * performing unrelated tasks. - * - *

All arguments to all task methods must be non-null. - * - *

jsr166e note: During transition, this class - * uses nested functional interfaces with different names but the - * same forms as those expected for JDK8. - * - *

This class is a member of the - * - * Java Collections Framework. - * - * @since 1.5 - * @author Doug Lea - * @param the type of keys maintained by this map - * @param the type of mapped values - */ -@SuppressWarnings("all") -public class ConcurrentHashMapV8 - implements ConcurrentMap, Serializable { - private static final long serialVersionUID = 7249069246763182397L; - - /** - * An object for traversing and partitioning elements of a source. - * This interface provides a subset of the functionality of JDK8 - * java.util.Spliterator. - */ - public static interface ConcurrentHashMapSpliterator { - /** - * If possible, returns a new spliterator covering - * approximately one half of the elements, which will not be - * covered by this spliterator. Returns null if cannot be - * split. - */ - ConcurrentHashMapSpliterator trySplit(); - /** - * Returns an estimate of the number of elements covered by - * this Spliterator. - */ - long estimateSize(); - - /** Applies the action to each untraversed element */ - void forEachRemaining(Action action); - /** If an element remains, applies the action and returns true. */ - boolean tryAdvance(Action action); - } - - // Sams - /** Interface describing a void action of one argument */ - public interface Action { void apply(A a); } - /** Interface describing a void action of two arguments */ - public interface BiAction { void apply(A a, B b); } - /** Interface describing a function of one argument */ - public interface Fun { T apply(A a); } - /** Interface describing a function of two arguments */ - public interface BiFun { T apply(A a, B b); } - /** Interface describing a function mapping its argument to a double */ - public interface ObjectToDouble { double apply(A a); } - /** Interface describing a function mapping its argument to a long */ - public interface ObjectToLong { long apply(A a); } - /** Interface describing a function mapping its argument to an int */ - public interface ObjectToInt {int apply(A a); } - /** Interface describing a function mapping two arguments to a double */ - public interface ObjectByObjectToDouble { double apply(A a, B b); } - /** Interface describing a function mapping two arguments to a long */ - public interface ObjectByObjectToLong { long apply(A a, B b); } - /** Interface describing a function mapping two arguments to an int */ - public interface ObjectByObjectToInt {int apply(A a, B b); } - /** Interface describing a function mapping two doubles to a double */ - public interface DoubleByDoubleToDouble { double apply(double a, double b); } - /** Interface describing a function mapping two longs to a long */ - public interface LongByLongToLong { long apply(long a, long b); } - /** Interface describing a function mapping two ints to an int */ - public interface IntByIntToInt { int apply(int a, int b); } - - /* - * Overview: - * - * The primary design goal of this hash table is to maintain - * concurrent readability (typically method get(), but also - * iterators and related methods) while minimizing update - * contention. Secondary goals are to keep space consumption about - * the same or better than java.util.HashMap, and to support high - * initial insertion rates on an empty table by many threads. - * - * This map usually acts as a binned (bucketed) hash table. Each - * key-value mapping is held in a Node. Most nodes are instances - * of the basic Node class with hash, key, value, and next - * fields. However, various subclasses exist: TreeNodes are - * arranged in balanced trees, not lists. TreeBins hold the roots - * of sets of TreeNodes. ForwardingNodes are placed at the heads - * of bins during resizing. ReservationNodes are used as - * placeholders while establishing values in computeIfAbsent and - * related methods. The types TreeBin, ForwardingNode, and - * ReservationNode do not hold normal user keys, values, or - * hashes, and are readily distinguishable during search etc - * because they have negative hash fields and null key and value - * fields. (These special nodes are either uncommon or transient, - * so the impact of carrying around some unused fields is - * insignificant.) - * - * The table is lazily initialized to a power-of-two size upon the - * first insertion. Each bin in the table normally contains a - * list of Nodes (most often, the list has only zero or one Node). - * Table accesses require volatile/atomic reads, writes, and - * CASes. Because there is no other way to arrange this without - * adding further indirections, we use intrinsics - * (sun.misc.Unsafe) operations. - * - * We use the top (sign) bit of Node hash fields for control - * purposes -- it is available anyway because of addressing - * constraints. Nodes with negative hash fields are specially - * handled or ignored in map methods. - * - * Insertion (via put or its variants) of the first node in an - * empty bin is performed by just CASing it to the bin. This is - * by far the most common case for put operations under most - * key/hash distributions. Other update operations (insert, - * delete, and replace) require locks. We do not want to waste - * the space required to associate a distinct lock object with - * each bin, so instead use the first node of a bin list itself as - * a lock. Locking support for these locks relies on builtin - * "synchronized" monitors. - * - * Using the first node of a list as a lock does not by itself - * suffice though: When a node is locked, any update must first - * validate that it is still the first node after locking it, and - * retry if not. Because new nodes are always appended to lists, - * once a node is first in a bin, it remains first until deleted - * or the bin becomes invalidated (upon resizing). - * - * The main disadvantage of per-bin locks is that other update - * operations on other nodes in a bin list protected by the same - * lock can stall, for example when user equals() or mapping - * functions take a long time. However, statistically, under - * random hash codes, this is not a common problem. Ideally, the - * frequency of nodes in bins follows a Poisson distribution - * (http://en.wikipedia.org/wiki/Poisson_distribution) with a - * parameter of about 0.5 on average, given the resizing threshold - * of 0.75, although with a large variance because of resizing - * granularity. Ignoring variance, the expected occurrences of - * list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The - * first values are: - * - * 0: 0.60653066 - * 1: 0.30326533 - * 2: 0.07581633 - * 3: 0.01263606 - * 4: 0.00157952 - * 5: 0.00015795 - * 6: 0.00001316 - * 7: 0.00000094 - * 8: 0.00000006 - * more: less than 1 in ten million - * - * Lock contention probability for two threads accessing distinct - * elements is roughly 1 / (8 * #elements) under random hashes. - * - * Actual hash code distributions encountered in practice - * sometimes deviate significantly from uniform randomness. This - * includes the case when N > (1<<30), so some keys MUST collide. - * Similarly for dumb or hostile usages in which multiple keys are - * designed to have identical hash codes or ones that differs only - * in masked-out high bits. So we use a secondary strategy that - * applies when the number of nodes in a bin exceeds a - * threshold. These TreeBins use a balanced tree to hold nodes (a - * specialized form of red-black trees), bounding search time to - * O(log N). Each search step in a TreeBin is at least twice as - * slow as in a regular list, but given that N cannot exceed - * (1<<64) (before running out of addresses) this bounds search - * steps, lock hold times, etc, to reasonable constants (roughly - * 100 nodes inspected per operation worst case) so long as keys - * are Comparable (which is very common -- String, Long, etc). - * TreeBin nodes (TreeNodes) also maintain the same "next" - * traversal pointers as regular nodes, so can be traversed in - * iterators in the same way. - * - * The table is resized when occupancy exceeds a percentage - * threshold (nominally, 0.75, but see below). Any thread - * noticing an overfull bin may assist in resizing after the - * initiating thread allocates and sets up the replacement - * array. However, rather than stalling, these other threads may - * proceed with insertions etc. The use of TreeBins shields us - * from the worst case effects of overfilling while resizes are in - * progress. Resizing proceeds by transferring bins, one by one, - * from the table to the next table. To enable concurrency, the - * next table must be (incrementally) prefilled with place-holders - * serving as reverse forwarders to the old table. Because we are - * using power-of-two expansion, the elements from each bin must - * either stay at same index, or move with a power of two - * offset. We eliminate unnecessary node creation by catching - * cases where old nodes can be reused because their next fields - * won't change. On average, only about one-sixth of them need - * cloning when a table doubles. The nodes they replace will be - * garbage collectable as soon as they are no longer referenced by - * any reader thread that may be in the midst of concurrently - * traversing table. Upon transfer, the old table bin contains - * only a special forwarding node (with hash field "MOVED") that - * contains the next table as its key. On encountering a - * forwarding node, access and update operations restart, using - * the new table. - * - * Each bin transfer requires its bin lock, which can stall - * waiting for locks while resizing. However, because other - * threads can join in and help resize rather than contend for - * locks, average aggregate waits become shorter as resizing - * progresses. The transfer operation must also ensure that all - * accessible bins in both the old and new table are usable by any - * traversal. This is arranged by proceeding from the last bin - * (table.length - 1) up towards the first. Upon seeing a - * forwarding node, traversals (see class Traverser) arrange to - * move to the new table without revisiting nodes. However, to - * ensure that no intervening nodes are skipped, bin splitting can - * only begin after the associated reverse-forwarders are in - * place. - * - * The traversal scheme also applies to partial traversals of - * ranges of bins (via an alternate Traverser constructor) - * to support partitioned aggregate operations. Also, read-only - * operations give up if ever forwarded to a null table, which - * provides support for shutdown-style clearing, which is also not - * currently implemented. - * - * Lazy table initialization minimizes footprint until first use, - * and also avoids resizings when the first operation is from a - * putAll, constructor with map argument, or deserialization. - * These cases attempt to override the initial capacity settings, - * but harmlessly fail to take effect in cases of races. - * - * The element count is maintained using a specialization of - * LongAdder. We need to incorporate a specialization rather than - * just use a LongAdder in order to access implicit - * contention-sensing that leads to creation of multiple - * CounterCells. The counter mechanics avoid contention on - * updates but can encounter cache thrashing if read too - * frequently during concurrent access. To avoid reading so often, - * resizing under contention is attempted only upon adding to a - * bin already holding two or more nodes. Under uniform hash - * distributions, the probability of this occurring at threshold - * is around 13%, meaning that only about 1 in 8 puts check - * threshold (and after resizing, many fewer do so). - * - * TreeBins use a special form of comparison for search and - * related operations (which is the main reason we cannot use - * existing collections such as TreeMaps). TreeBins contain - * Comparable elements, but may contain others, as well as - * elements that are Comparable but not necessarily Comparable - * for the same T, so we cannot invoke compareTo among them. To - * handle this, the tree is ordered primarily by hash value, then - * by Comparable.compareTo order if applicable. On lookup at a - * node, if elements are not comparable or compare as 0 then both - * left and right children may need to be searched in the case of - * tied hash values. (This corresponds to the full list search - * that would be necessary if all elements were non-Comparable and - * had tied hashes.) The red-black balancing code is updated from - * pre-jdk-collections - * (http://gee.cs.oswego.edu/dl/classes/collections/RBCell.java) - * based in turn on Cormen, Leiserson, and Rivest "Introduction to - * Algorithms" (CLR). - * - * TreeBins also require an additional locking mechanism. While - * list traversal is always possible by readers even during - * updates, tree traversal is not, mainly because of tree-rotations - * that may change the root node and/or its linkages. TreeBins - * include a simple read-write lock mechanism parasitic on the - * main bin-synchronization strategy: Structural adjustments - * associated with an insertion or removal are already bin-locked - * (and so cannot conflict with other writers) but must wait for - * ongoing readers to finish. Since there can be only one such - * waiter, we use a simple scheme using a single "waiter" field to - * block writers. However, readers need never block. If the root - * lock is held, they proceed along the slow traversal path (via - * next-pointers) until the lock becomes available or the list is - * exhausted, whichever comes first. These cases are not fast, but - * maximize aggregate expected throughput. - * - * Maintaining API and serialization compatibility with previous - * versions of this class introduces several oddities. Mainly: We - * leave untouched but unused constructor arguments refering to - * concurrencyLevel. We accept a loadFactor constructor argument, - * but apply it only to initial table capacity (which is the only - * time that we can guarantee to honor it.) We also declare an - * unused "Segment" class that is instantiated in minimal form - * only when serializing. - * - * This file is organized to make things a little easier to follow - * while reading than they might otherwise: First the main static - * declarations and utilities, then fields, then main public - * methods (with a few factorings of multiple public methods into - * internal ones), then sizing methods, trees, traversers, and - * bulk operations. - */ - - /* ---------------- Constants -------------- */ - - /** - * The largest possible table capacity. This value must be - * exactly 1<<30 to stay within Java array allocation and indexing - * bounds for power of two table sizes, and is further required - * because the top two bits of 32bit hash fields are used for - * control purposes. - */ - private static final int MAXIMUM_CAPACITY = 1 << 30; - - /** - * The default initial table capacity. Must be a power of 2 - * (i.e., at least 1) and at most MAXIMUM_CAPACITY. - */ - private static final int DEFAULT_CAPACITY = 16; - - /** - * The largest possible (non-power of two) array size. - * Needed by toArray and related methods. - */ - static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; - - /** - * The default concurrency level for this table. Unused but - * defined for compatibility with previous versions of this class. - */ - private static final int DEFAULT_CONCURRENCY_LEVEL = 16; - - /** - * The load factor for this table. Overrides of this value in - * constructors affect only the initial table capacity. The - * actual floating point value isn't normally used -- it is - * simpler to use expressions such as {@code n - (n >>> 2)} for - * the associated resizing threshold. - */ - private static final float LOAD_FACTOR = 0.75f; - - /** - * The bin count threshold for using a tree rather than list for a - * bin. Bins are converted to trees when adding an element to a - * bin with at least this many nodes. The value must be greater - * than 2, and should be at least 8 to mesh with assumptions in - * tree removal about conversion back to plain bins upon - * shrinkage. - */ - static final int TREEIFY_THRESHOLD = 8; - - /** - * The bin count threshold for untreeifying a (split) bin during a - * resize operation. Should be less than TREEIFY_THRESHOLD, and at - * most 6 to mesh with shrinkage detection under removal. - */ - static final int UNTREEIFY_THRESHOLD = 6; - - /** - * The smallest table capacity for which bins may be treeified. - * (Otherwise the table is resized if too many nodes in a bin.) - * The value should be at least 4 * TREEIFY_THRESHOLD to avoid - * conflicts between resizing and treeification thresholds. - */ - static final int MIN_TREEIFY_CAPACITY = 64; - - /** - * Minimum number of rebinnings per transfer step. Ranges are - * subdivided to allow multiple resizer threads. This value - * serves as a lower bound to avoid resizers encountering - * excessive memory contention. The value should be at least - * DEFAULT_CAPACITY. - */ - private static final int MIN_TRANSFER_STRIDE = 16; - - /* - * Encodings for Node hash fields. See above for explanation. - */ - static final int MOVED = -1; // hash for forwarding nodes - static final int TREEBIN = -2; // hash for roots of trees - static final int RESERVED = -3; // hash for transient reservations - static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash - - /** Number of CPUS, to place bounds on some sizings */ - static final int NCPU = Runtime.getRuntime().availableProcessors(); - - /** For serialization compatibility. */ - private static final ObjectStreamField[] serialPersistentFields = { - new ObjectStreamField("segments", Segment[].class), - new ObjectStreamField("segmentMask", Integer.TYPE), - new ObjectStreamField("segmentShift", Integer.TYPE) - }; - - /* ---------------- Nodes -------------- */ - - /** - * Key-value entry. This class is never exported out as a - * user-mutable Map.Entry (i.e., one supporting setValue; see - * MapEntry below), but can be used for read-only traversals used - * in bulk tasks. Subclasses of Node with a negative hash field - * are special, and contain null keys and values (but are never - * exported). Otherwise, keys and vals are never null. - */ - static class Node implements Map.Entry { - final int hash; - final K key; - volatile V val; - volatile Node next; - - Node(int hash, K key, V val, Node next) { - this.hash = hash; - this.key = key; - this.val = val; - this.next = next; - } - - public final K getKey() { return key; } - public final V getValue() { return val; } - public final int hashCode() { return key.hashCode() ^ val.hashCode(); } - public final String toString(){ return key + "=" + val; } - public final V setValue(V value) { - throw new UnsupportedOperationException(); - } - - public final boolean equals(Object o) { - Object k, v, u; Map.Entry e; - return ((o instanceof Map.Entry) && - (k = (e = (Map.Entry)o).getKey()) != null && - (v = e.getValue()) != null && - (k == key || k.equals(key)) && - (v == (u = val) || v.equals(u))); - } - - /** - * Virtualized support for map.get(); overridden in subclasses. - */ - Node find(int h, Object k) { - Node e = this; - if (k != null) { - do { - K ek; - if (e.hash == h && - ((ek = e.key) == k || (ek != null && k.equals(ek)))) - return e; - } while ((e = e.next) != null); - } - return null; - } - } - - /* ---------------- Static utilities -------------- */ - - /** - * Spreads (XORs) higher bits of hash to lower and also forces top - * bit to 0. Because the table uses power-of-two masking, sets of - * hashes that vary only in bits above the current mask will - * always collide. (Among known examples are sets of Float keys - * holding consecutive whole numbers in small tables.) So we - * apply a transform that spreads the impact of higher bits - * downward. There is a tradeoff between speed, utility, and - * quality of bit-spreading. Because many common sets of hashes - * are already reasonably distributed (so don't benefit from - * spreading), and because we use trees to handle large sets of - * collisions in bins, we just XOR some shifted bits in the - * cheapest possible way to reduce systematic lossage, as well as - * to incorporate impact of the highest bits that would otherwise - * never be used in index calculations because of table bounds. - */ - static final int spread(int h) { - return (h ^ (h >>> 16)) & HASH_BITS; - } - - /** - * Returns a power of two table size for the given desired capacity. - * See Hackers Delight, sec 3.2 - */ - private static final int tableSizeFor(int c) { - int n = c - 1; - n |= n >>> 1; - n |= n >>> 2; - n |= n >>> 4; - n |= n >>> 8; - n |= n >>> 16; - return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; - } - - /** - * Returns x's Class if it is of the form "class C implements - * Comparable", else null. - */ - static Class comparableClassFor(Object x) { - if (x instanceof Comparable) { - Class c; Type[] ts, as; Type t; ParameterizedType p; - if ((c = x.getClass()) == String.class) // bypass checks - return c; - if ((ts = c.getGenericInterfaces()) != null) { - for (int i = 0; i < ts.length; ++i) { - if (((t = ts[i]) instanceof ParameterizedType) && - ((p = (ParameterizedType)t).getRawType() == - Comparable.class) && - (as = p.getActualTypeArguments()) != null && - as.length == 1 && as[0] == c) // type arg is c - return c; - } - } - } - return null; - } - - /** - * Returns k.compareTo(x) if x matches kc (k's screened comparable - * class), else 0. - */ - @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable - static int compareComparables(Class kc, Object k, Object x) { - return (x == null || x.getClass() != kc ? 0 : - ((Comparable)k).compareTo(x)); - } - - /* ---------------- Table element access -------------- */ - - /* - * Volatile access methods are used for table elements as well as - * elements of in-progress next table while resizing. All uses of - * the tab arguments must be null checked by callers. All callers - * also paranoically precheck that tab's length is not zero (or an - * equivalent check), thus ensuring that any index argument taking - * the form of a hash value anded with (length - 1) is a valid - * index. Note that, to be correct wrt arbitrary concurrency - * errors by users, these checks must operate on local variables, - * which accounts for some odd-looking inline assignments below. - * Note that calls to setTabAt always occur within locked regions, - * and so in principle require only release ordering, not need - * full volatile semantics, but are currently coded as volatile - * writes to be conservative. - */ - - @SuppressWarnings("unchecked") - static final Node tabAt(Node[] tab, int i) { - return (Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); - } - - static final boolean casTabAt(Node[] tab, int i, - Node c, Node v) { - return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); - } - - static final void setTabAt(Node[] tab, int i, Node v) { - U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); - } - - /* ---------------- Fields -------------- */ - - /** - * The array of bins. Lazily initialized upon first insertion. - * Size is always a power of two. Accessed directly by iterators. - */ - transient volatile Node[] table; - - /** - * The next table to use; non-null only while resizing. - */ - private transient volatile Node[] nextTable; - - /** - * Base counter value, used mainly when there is no contention, - * but also as a fallback during table initialization - * races. Updated via CAS. - */ - private transient volatile long baseCount; - - /** - * Table initialization and resizing control. When negative, the - * table is being initialized or resized: -1 for initialization, - * else -(1 + the number of active resizing threads). Otherwise, - * when table is null, holds the initial table size to use upon - * creation, or 0 for default. After initialization, holds the - * next element count value upon which to resize the table. - */ - private transient volatile int sizeCtl; - - /** - * The next table index (plus one) to split while resizing. - */ - private transient volatile int transferIndex; - - /** - * The least available table index to split while resizing. - */ - private transient volatile int transferOrigin; - - /** - * Spinlock (locked via CAS) used when resizing and/or creating CounterCells. - */ - private transient volatile int cellsBusy; - - /** - * Table of counter cells. When non-null, size is a power of 2. - */ - private transient volatile CounterCell[] counterCells; - - // views - private transient KeySetView keySet; - private transient ValuesView values; - private transient EntrySetView entrySet; - - - /* ---------------- Public operations -------------- */ - - /** - * Creates a new, empty map with the default initial table size (16). - */ - public ConcurrentHashMapV8() { - } - - /** - * Creates a new, empty map with an initial table size - * accommodating the specified number of elements without the need - * to dynamically resize. - * - * @param initialCapacity The implementation performs internal - * sizing to accommodate this many elements. - * @throws IllegalArgumentException if the initial capacity of - * elements is negative - */ - public ConcurrentHashMapV8(int initialCapacity) { - if (initialCapacity < 0) - throw new IllegalArgumentException(); - int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? - MAXIMUM_CAPACITY : - tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); - this.sizeCtl = cap; - } - - /** - * Creates a new map with the same mappings as the given map. - * - * @param m the map - */ - public ConcurrentHashMapV8(Map m) { - this.sizeCtl = DEFAULT_CAPACITY; - putAll(m); - } - - /** - * Creates a new, empty map with an initial table size based on - * the given number of elements ({@code initialCapacity}) and - * initial table density ({@code loadFactor}). - * - * @param initialCapacity the initial capacity. The implementation - * performs internal sizing to accommodate this many elements, - * given the specified load factor. - * @param loadFactor the load factor (table density) for - * establishing the initial table size - * @throws IllegalArgumentException if the initial capacity of - * elements is negative or the load factor is nonpositive - * - * @since 1.6 - */ - public ConcurrentHashMapV8(int initialCapacity, float loadFactor) { - this(initialCapacity, loadFactor, 1); - } - - /** - * Creates a new, empty map with an initial table size based on - * the given number of elements ({@code initialCapacity}), table - * density ({@code loadFactor}), and number of concurrently - * updating threads ({@code concurrencyLevel}). - * - * @param initialCapacity the initial capacity. The implementation - * performs internal sizing to accommodate this many elements, - * given the specified load factor. - * @param loadFactor the load factor (table density) for - * establishing the initial table size - * @param concurrencyLevel the estimated number of concurrently - * updating threads. The implementation may use this value as - * a sizing hint. - * @throws IllegalArgumentException if the initial capacity is - * negative or the load factor or concurrencyLevel are - * nonpositive - */ - public ConcurrentHashMapV8(int initialCapacity, - float loadFactor, int concurrencyLevel) { - if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) - throw new IllegalArgumentException(); - if (initialCapacity < concurrencyLevel) // Use at least as many bins - initialCapacity = concurrencyLevel; // as estimated threads - long size = (long)(1.0 + (long)initialCapacity / loadFactor); - int cap = (size >= (long)MAXIMUM_CAPACITY) ? - MAXIMUM_CAPACITY : tableSizeFor((int)size); - this.sizeCtl = cap; - } - - // Original (since JDK1.2) Map methods - - /** - * {@inheritDoc} - */ - public int size() { - long n = sumCount(); - return ((n < 0L) ? 0 : - (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : - (int)n); - } - - /** - * {@inheritDoc} - */ - public boolean isEmpty() { - return sumCount() <= 0L; // ignore transient negative values - } - - /** - * Returns the value to which the specified key is mapped, - * or {@code null} if this map contains no mapping for the key. - * - *

More formally, if this map contains a mapping from a key - * {@code k} to a value {@code v} such that {@code key.equals(k)}, - * then this method returns {@code v}; otherwise it returns - * {@code null}. (There can be at most one such mapping.) - * - * @throws NullPointerException if the specified key is null - */ - public V get(Object key) { - Node[] tab; Node e, p; int n, eh; K ek; - int h = spread(key.hashCode()); - if ((tab = table) != null && (n = tab.length) > 0 && - (e = tabAt(tab, (n - 1) & h)) != null) { - if ((eh = e.hash) == h) { - if ((ek = e.key) == key || (ek != null && key.equals(ek))) - return e.val; - } - else if (eh < 0) - return (p = e.find(h, key)) != null ? p.val : null; - while ((e = e.next) != null) { - if (e.hash == h && - ((ek = e.key) == key || (ek != null && key.equals(ek)))) - return e.val; - } - } - return null; - } - - /** - * Tests if the specified object is a key in this table. - * - * @param key possible key - * @return {@code true} if and only if the specified object - * is a key in this table, as determined by the - * {@code equals} method; {@code false} otherwise - * @throws NullPointerException if the specified key is null - */ - public boolean containsKey(Object key) { - return get(key) != null; - } - - /** - * Returns {@code true} if this map maps one or more keys to the - * specified value. Note: This method may require a full traversal - * of the map, and is much slower than method {@code containsKey}. - * - * @param value value whose presence in this map is to be tested - * @return {@code true} if this map maps one or more keys to the - * specified value - * @throws NullPointerException if the specified value is null - */ - public boolean containsValue(Object value) { - if (value == null) - throw new NullPointerException(); - Node[] t; - if ((t = table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - V v; - if ((v = p.val) == value || (v != null && value.equals(v))) - return true; - } - } - return false; - } - - /** - * Maps the specified key to the specified value in this table. - * Neither the key nor the value can be null. - * - *

The value can be retrieved by calling the {@code get} method - * with a key that is equal to the original key. - * - * @param key key with which the specified value is to be associated - * @param value value to be associated with the specified key - * @return the previous value associated with {@code key}, or - * {@code null} if there was no mapping for {@code key} - * @throws NullPointerException if the specified key or value is null - */ - public V put(K key, V value) { - return putVal(key, value, false); - } - - /** Implementation for put and putIfAbsent */ - final V putVal(K key, V value, boolean onlyIfAbsent) { - if (key == null || value == null) throw new NullPointerException(); - int hash = spread(key.hashCode()); - int binCount = 0; - for (Node[] tab = table;;) { - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0) - tab = initTable(); - else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { - if (casTabAt(tab, i, null, - new Node(hash, key, value, null))) - break; // no lock when adding to empty bin - } - else if ((fh = f.hash) == MOVED) - tab = helpTransfer(tab, f); - else { - V oldVal = null; - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f;; ++binCount) { - K ek; - if (e.hash == hash && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { - oldVal = e.val; - if (!onlyIfAbsent) - e.val = value; - break; - } - Node pred = e; - if ((e = e.next) == null) { - pred.next = new Node(hash, key, - value, null); - break; - } - } - } - else if (f instanceof TreeBin) { - Node p; - binCount = 2; - if ((p = ((TreeBin)f).putTreeVal(hash, key, - value)) != null) { - oldVal = p.val; - if (!onlyIfAbsent) - p.val = value; - } - } - } - } - if (binCount != 0) { - if (binCount >= TREEIFY_THRESHOLD) - treeifyBin(tab, i); - if (oldVal != null) - return oldVal; - break; - } - } - } - addCount(1L, binCount); - return null; - } - - /** - * Copies all of the mappings from the specified map to this one. - * These mappings replace any mappings that this map had for any of the - * keys currently in the specified map. - * - * @param m mappings to be stored in this map - */ - public void putAll(Map m) { - tryPresize(m.size()); - for (Map.Entry e : m.entrySet()) - putVal(e.getKey(), e.getValue(), false); - } - - /** - * Removes the key (and its corresponding value) from this map. - * This method does nothing if the key is not in the map. - * - * @param key the key that needs to be removed - * @return the previous value associated with {@code key}, or - * {@code null} if there was no mapping for {@code key} - * @throws NullPointerException if the specified key is null - */ - public V remove(Object key) { - return replaceNode(key, null, null); - } - - /** - * Implementation for the four public remove/replace methods: - * Replaces node value with v, conditional upon match of cv if - * non-null. If resulting value is null, delete. - */ - final V replaceNode(Object key, V value, Object cv) { - int hash = spread(key.hashCode()); - for (Node[] tab = table;;) { - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0 || - (f = tabAt(tab, i = (n - 1) & hash)) == null) - break; - else if ((fh = f.hash) == MOVED) - tab = helpTransfer(tab, f); - else { - V oldVal = null; - boolean validated = false; - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - validated = true; - for (Node e = f, pred = null;;) { - K ek; - if (e.hash == hash && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { - V ev = e.val; - if (cv == null || cv == ev || - (ev != null && cv.equals(ev))) { - oldVal = ev; - if (value != null) - e.val = value; - else if (pred != null) - pred.next = e.next; - else - setTabAt(tab, i, e.next); - } - break; - } - pred = e; - if ((e = e.next) == null) - break; - } - } - else if (f instanceof TreeBin) { - validated = true; - TreeBin t = (TreeBin)f; - TreeNode r, p; - if ((r = t.root) != null && - (p = r.findTreeNode(hash, key, null)) != null) { - V pv = p.val; - if (cv == null || cv == pv || - (pv != null && cv.equals(pv))) { - oldVal = pv; - if (value != null) - p.val = value; - else if (t.removeTreeNode(p)) - setTabAt(tab, i, untreeify(t.first)); - } - } - } - } - } - if (validated) { - if (oldVal != null) { - if (value == null) - addCount(-1L, -1); - return oldVal; - } - break; - } - } - } - return null; - } - - /** - * Removes all of the mappings from this map. - */ - public void clear() { - long delta = 0L; // negative number of deletions - int i = 0; - Node[] tab = table; - while (tab != null && i < tab.length) { - int fh; - Node f = tabAt(tab, i); - if (f == null) - ++i; - else if ((fh = f.hash) == MOVED) { - tab = helpTransfer(tab, f); - i = 0; // restart - } - else { - synchronized (f) { - if (tabAt(tab, i) == f) { - Node p = (fh >= 0 ? f : - (f instanceof TreeBin) ? - ((TreeBin)f).first : null); - while (p != null) { - --delta; - p = p.next; - } - setTabAt(tab, i++, null); - } - } - } - } - if (delta != 0L) - addCount(delta, -1); - } - - /** - * Returns a {@link Set} view of the keys contained in this map. - * The set is backed by the map, so changes to the map are - * reflected in the set, and vice-versa. The set supports element - * removal, which removes the corresponding mapping from this map, - * via the {@code Iterator.remove}, {@code Set.remove}, - * {@code removeAll}, {@code retainAll}, and {@code clear} - * operations. It does not support the {@code add} or - * {@code addAll} operations. - * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. - * - * @return the set view - */ - public KeySetView keySet() { - KeySetView ks; - return (ks = keySet) != null ? ks : (keySet = new KeySetView(this, null)); - } - - /** - * Returns a {@link Collection} view of the values contained in this map. - * The collection is backed by the map, so changes to the map are - * reflected in the collection, and vice-versa. The collection - * supports element removal, which removes the corresponding - * mapping from this map, via the {@code Iterator.remove}, - * {@code Collection.remove}, {@code removeAll}, - * {@code retainAll}, and {@code clear} operations. It does not - * support the {@code add} or {@code addAll} operations. - * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. - * - * @return the collection view - */ - public Collection values() { - ValuesView vs; - return (vs = values) != null ? vs : (values = new ValuesView(this)); - } - - /** - * Returns a {@link Set} view of the mappings contained in this map. - * The set is backed by the map, so changes to the map are - * reflected in the set, and vice-versa. The set supports element - * removal, which removes the corresponding mapping from the map, - * via the {@code Iterator.remove}, {@code Set.remove}, - * {@code removeAll}, {@code retainAll}, and {@code clear} - * operations. - * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. - * - * @return the set view - */ - public Set> entrySet() { - EntrySetView es; - return (es = entrySet) != null ? es : (entrySet = new EntrySetView(this)); - } - - /** - * Returns the hash code value for this {@link Map}, i.e., - * the sum of, for each key-value pair in the map, - * {@code key.hashCode() ^ value.hashCode()}. - * - * @return the hash code value for this map - */ - public int hashCode() { - int h = 0; - Node[] t; - if ((t = table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) - h += p.key.hashCode() ^ p.val.hashCode(); - } - return h; - } - - /** - * Returns a string representation of this map. The string - * representation consists of a list of key-value mappings (in no - * particular order) enclosed in braces ("{@code {}}"). Adjacent - * mappings are separated by the characters {@code ", "} (comma - * and space). Each key-value mapping is rendered as the key - * followed by an equals sign ("{@code =}") followed by the - * associated value. - * - * @return a string representation of this map - */ - public String toString() { - Node[] t; - int f = (t = table) == null ? 0 : t.length; - Traverser it = new Traverser(t, f, 0, f); - StringBuilder sb = new StringBuilder(); - sb.append('{'); - Node p; - if ((p = it.advance()) != null) { - for (;;) { - K k = p.key; - V v = p.val; - sb.append(k == this ? "(this Map)" : k); - sb.append('='); - sb.append(v == this ? "(this Map)" : v); - if ((p = it.advance()) == null) - break; - sb.append(',').append(' '); - } - } - return sb.append('}').toString(); - } - - /** - * Compares the specified object with this map for equality. - * Returns {@code true} if the given object is a map with the same - * mappings as this map. This operation may return misleading - * results if either map is concurrently modified during execution - * of this method. - * - * @param o object to be compared for equality with this map - * @return {@code true} if the specified object is equal to this map - */ - public boolean equals(Object o) { - if (o != this) { - if (!(o instanceof Map)) - return false; - Map m = (Map) o; - Node[] t; - int f = (t = table) == null ? 0 : t.length; - Traverser it = new Traverser(t, f, 0, f); - for (Node p; (p = it.advance()) != null; ) { - V val = p.val; - Object v = m.get(p.key); - if (v == null || (v != val && !v.equals(val))) - return false; - } - for (Map.Entry e : m.entrySet()) { - Object mk, mv, v; - if ((mk = e.getKey()) == null || - (mv = e.getValue()) == null || - (v = get(mk)) == null || - (mv != v && !mv.equals(v))) - return false; - } - } - return true; - } - - /** - * Stripped-down version of helper class used in previous version, - * declared for the sake of serialization compatibility - */ - static class Segment extends ReentrantLock implements Serializable { - private static final long serialVersionUID = 2249069246763182397L; - final float loadFactor; - Segment(float lf) { this.loadFactor = lf; } - } - - /** - * Saves the state of the {@code ConcurrentHashMapV8} instance to a - * stream (i.e., serializes it). - * @param s the stream - * @serialData - * the key (Object) and value (Object) - * for each key-value mapping, followed by a null pair. - * The key-value mappings are emitted in no particular order. - */ - private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException { - // For serialization compatibility - // Emulate segment calculation from previous version of this class - int sshift = 0; - int ssize = 1; - while (ssize < DEFAULT_CONCURRENCY_LEVEL) { - ++sshift; - ssize <<= 1; - } - int segmentShift = 32 - sshift; - int segmentMask = ssize - 1; - @SuppressWarnings("unchecked") Segment[] segments = (Segment[]) - new Segment[DEFAULT_CONCURRENCY_LEVEL]; - for (int i = 0; i < segments.length; ++i) - segments[i] = new Segment(LOAD_FACTOR); - s.putFields().put("segments", segments); - s.putFields().put("segmentShift", segmentShift); - s.putFields().put("segmentMask", segmentMask); - s.writeFields(); - - Node[] t; - if ((t = table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - s.writeObject(p.key); - s.writeObject(p.val); - } - } - s.writeObject(null); - s.writeObject(null); - segments = null; // throw away - } - - /** - * Reconstitutes the instance from a stream (that is, deserializes it). - * @param s the stream - */ - private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - /* - * To improve performance in typical cases, we create nodes - * while reading, then place in table once size is known. - * However, we must also validate uniqueness and deal with - * overpopulated bins while doing so, which requires - * specialized versions of putVal mechanics. - */ - sizeCtl = -1; // force exclusion for table construction - s.defaultReadObject(); - long size = 0L; - Node p = null; - for (;;) { - @SuppressWarnings("unchecked") K k = (K) s.readObject(); - @SuppressWarnings("unchecked") V v = (V) s.readObject(); - if (k != null && v != null) { - p = new Node(spread(k.hashCode()), k, v, p); - ++size; - } - else - break; - } - if (size == 0L) - sizeCtl = 0; - else { - int n; - if (size >= (long)(MAXIMUM_CAPACITY >>> 1)) - n = MAXIMUM_CAPACITY; - else { - int sz = (int)size; - n = tableSizeFor(sz + (sz >>> 1) + 1); - } - @SuppressWarnings({"rawtypes","unchecked"}) - Node[] tab = (Node[])new Node[n]; - int mask = n - 1; - long added = 0L; - while (p != null) { - boolean insertAtFront; - Node next = p.next, first; - int h = p.hash, j = h & mask; - if ((first = tabAt(tab, j)) == null) - insertAtFront = true; - else { - K k = p.key; - if (first.hash < 0) { - TreeBin t = (TreeBin)first; - if (t.putTreeVal(h, k, p.val) == null) - ++added; - insertAtFront = false; - } - else { - int binCount = 0; - insertAtFront = true; - Node q; K qk; - for (q = first; q != null; q = q.next) { - if (q.hash == h && - ((qk = q.key) == k || - (qk != null && k.equals(qk)))) { - insertAtFront = false; - break; - } - ++binCount; - } - if (insertAtFront && binCount >= TREEIFY_THRESHOLD) { - insertAtFront = false; - ++added; - p.next = first; - TreeNode hd = null, tl = null; - for (q = p; q != null; q = q.next) { - TreeNode t = new TreeNode - (q.hash, q.key, q.val, null, null); - if ((t.prev = tl) == null) - hd = t; - else - tl.next = t; - tl = t; - } - setTabAt(tab, j, new TreeBin(hd)); - } - } - } - if (insertAtFront) { - ++added; - p.next = first; - setTabAt(tab, j, p); - } - p = next; - } - table = tab; - sizeCtl = n - (n >>> 2); - baseCount = added; - } - } - - // ConcurrentMap methods - - /** - * {@inheritDoc} - * - * @return the previous value associated with the specified key, - * or {@code null} if there was no mapping for the key - * @throws NullPointerException if the specified key or value is null - */ - public V putIfAbsent(K key, V value) { - return putVal(key, value, true); - } - - /** - * {@inheritDoc} - * - * @throws NullPointerException if the specified key is null - */ - public boolean remove(Object key, Object value) { - if (key == null) - throw new NullPointerException(); - return value != null && replaceNode(key, null, value) != null; - } - - /** - * {@inheritDoc} - * - * @throws NullPointerException if any of the arguments are null - */ - public boolean replace(K key, V oldValue, V newValue) { - if (key == null || oldValue == null || newValue == null) - throw new NullPointerException(); - return replaceNode(key, newValue, oldValue) != null; - } - - /** - * {@inheritDoc} - * - * @return the previous value associated with the specified key, - * or {@code null} if there was no mapping for the key - * @throws NullPointerException if the specified key or value is null - */ - public V replace(K key, V value) { - if (key == null || value == null) - throw new NullPointerException(); - return replaceNode(key, value, null); - } - - // Overrides of JDK8+ Map extension method defaults - - /** - * Returns the value to which the specified key is mapped, or the - * given default value if this map contains no mapping for the - * key. - * - * @param key the key whose associated value is to be returned - * @param defaultValue the value to return if this map contains - * no mapping for the given key - * @return the mapping for the key, if present; else the default value - * @throws NullPointerException if the specified key is null - */ - public V getOrDefault(Object key, V defaultValue) { - V v; - return (v = get(key)) == null ? defaultValue : v; - } - - public void forEach(BiAction action) { - if (action == null) throw new NullPointerException(); - Node[] t; - if ((t = table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - action.apply(p.key, p.val); - } - } - } - - public void replaceAll(BiFun function) { - if (function == null) throw new NullPointerException(); - Node[] t; - if ((t = table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - V oldValue = p.val; - for (K key = p.key;;) { - V newValue = function.apply(key, oldValue); - if (newValue == null) - throw new NullPointerException(); - if (replaceNode(key, newValue, oldValue) != null || - (oldValue = get(key)) == null) - break; - } - } - } - } - - /** - * If the specified key is not already associated with a value, - * attempts to compute its value using the given mapping function - * and enters it into this map unless {@code null}. The entire - * method invocation is performed atomically, so the function is - * applied at most once per key. Some attempted update operations - * on this map by other threads may be blocked while computation - * is in progress, so the computation should be short and simple, - * and must not attempt to update any other mappings of this map. - * - * @param key key with which the specified value is to be associated - * @param mappingFunction the function to compute a value - * @return the current (existing or computed) value associated with - * the specified key, or null if the computed value is null - * @throws NullPointerException if the specified key or mappingFunction - * is null - * @throws IllegalStateException if the computation detectably - * attempts a recursive update to this map that would - * otherwise never complete - * @throws RuntimeException or Error if the mappingFunction does so, - * in which case the mapping is left unestablished - */ - public V computeIfAbsent(K key, Fun mappingFunction) { - if (key == null || mappingFunction == null) - throw new NullPointerException(); - int h = spread(key.hashCode()); - V val = null; - int binCount = 0; - for (Node[] tab = table;;) { - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0) - tab = initTable(); - else if ((f = tabAt(tab, i = (n - 1) & h)) == null) { - Node r = new ReservationNode(); - synchronized (r) { - if (casTabAt(tab, i, null, r)) { - binCount = 1; - Node node = null; - try { - if ((val = mappingFunction.apply(key)) != null) - node = new Node(h, key, val, null); - } finally { - setTabAt(tab, i, node); - } - } - } - if (binCount != 0) - break; - } - else if ((fh = f.hash) == MOVED) - tab = helpTransfer(tab, f); - else { - boolean added = false; - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f;; ++binCount) { - K ek; V ev; - if (e.hash == h && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { - val = e.val; - break; - } - Node pred = e; - if ((e = e.next) == null) { - if ((val = mappingFunction.apply(key)) != null) { - added = true; - pred.next = new Node(h, key, val, null); - } - break; - } - } - } - else if (f instanceof TreeBin) { - binCount = 2; - TreeBin t = (TreeBin)f; - TreeNode r, p; - if ((r = t.root) != null && - (p = r.findTreeNode(h, key, null)) != null) - val = p.val; - else if ((val = mappingFunction.apply(key)) != null) { - added = true; - t.putTreeVal(h, key, val); - } - } - } - } - if (binCount != 0) { - if (binCount >= TREEIFY_THRESHOLD) - treeifyBin(tab, i); - if (!added) - return val; - break; - } - } - } - if (val != null) - addCount(1L, binCount); - return val; - } - - /** - * If the value for the specified key is present, attempts to - * compute a new mapping given the key and its current mapped - * value. The entire method invocation is performed atomically. - * Some attempted update operations on this map by other threads - * may be blocked while computation is in progress, so the - * computation should be short and simple, and must not attempt to - * update any other mappings of this map. - * - * @param key key with which a value may be associated - * @param remappingFunction the function to compute a value - * @return the new value associated with the specified key, or null if none - * @throws NullPointerException if the specified key or remappingFunction - * is null - * @throws IllegalStateException if the computation detectably - * attempts a recursive update to this map that would - * otherwise never complete - * @throws RuntimeException or Error if the remappingFunction does so, - * in which case the mapping is unchanged - */ - public V computeIfPresent(K key, BiFun remappingFunction) { - if (key == null || remappingFunction == null) - throw new NullPointerException(); - int h = spread(key.hashCode()); - V val = null; - int delta = 0; - int binCount = 0; - for (Node[] tab = table;;) { - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0) - tab = initTable(); - else if ((f = tabAt(tab, i = (n - 1) & h)) == null) - break; - else if ((fh = f.hash) == MOVED) - tab = helpTransfer(tab, f); - else { - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f, pred = null;; ++binCount) { - K ek; - if (e.hash == h && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { - val = remappingFunction.apply(key, e.val); - if (val != null) - e.val = val; - else { - delta = -1; - Node en = e.next; - if (pred != null) - pred.next = en; - else - setTabAt(tab, i, en); - } - break; - } - pred = e; - if ((e = e.next) == null) - break; - } - } - else if (f instanceof TreeBin) { - binCount = 2; - TreeBin t = (TreeBin)f; - TreeNode r, p; - if ((r = t.root) != null && - (p = r.findTreeNode(h, key, null)) != null) { - val = remappingFunction.apply(key, p.val); - if (val != null) - p.val = val; - else { - delta = -1; - if (t.removeTreeNode(p)) - setTabAt(tab, i, untreeify(t.first)); - } - } - } - } - } - if (binCount != 0) - break; - } - } - if (delta != 0) - addCount((long)delta, binCount); - return val; - } - - /** - * Attempts to compute a mapping for the specified key and its - * current mapped value (or {@code null} if there is no current - * mapping). The entire method invocation is performed atomically. - * Some attempted update operations on this map by other threads - * may be blocked while computation is in progress, so the - * computation should be short and simple, and must not attempt to - * update any other mappings of this Map. - * - * @param key key with which the specified value is to be associated - * @param remappingFunction the function to compute a value - * @return the new value associated with the specified key, or null if none - * @throws NullPointerException if the specified key or remappingFunction - * is null - * @throws IllegalStateException if the computation detectably - * attempts a recursive update to this map that would - * otherwise never complete - * @throws RuntimeException or Error if the remappingFunction does so, - * in which case the mapping is unchanged - */ - public V compute(K key, - BiFun remappingFunction) { - if (key == null || remappingFunction == null) - throw new NullPointerException(); - int h = spread(key.hashCode()); - V val = null; - int delta = 0; - int binCount = 0; - for (Node[] tab = table;;) { - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0) - tab = initTable(); - else if ((f = tabAt(tab, i = (n - 1) & h)) == null) { - Node r = new ReservationNode(); - synchronized (r) { - if (casTabAt(tab, i, null, r)) { - binCount = 1; - Node node = null; - try { - if ((val = remappingFunction.apply(key, null)) != null) { - delta = 1; - node = new Node(h, key, val, null); - } - } finally { - setTabAt(tab, i, node); - } - } - } - if (binCount != 0) - break; - } - else if ((fh = f.hash) == MOVED) - tab = helpTransfer(tab, f); - else { - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f, pred = null;; ++binCount) { - K ek; - if (e.hash == h && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { - val = remappingFunction.apply(key, e.val); - if (val != null) - e.val = val; - else { - delta = -1; - Node en = e.next; - if (pred != null) - pred.next = en; - else - setTabAt(tab, i, en); - } - break; - } - pred = e; - if ((e = e.next) == null) { - val = remappingFunction.apply(key, null); - if (val != null) { - delta = 1; - pred.next = - new Node(h, key, val, null); - } - break; - } - } - } - else if (f instanceof TreeBin) { - binCount = 1; - TreeBin t = (TreeBin)f; - TreeNode r, p; - if ((r = t.root) != null) - p = r.findTreeNode(h, key, null); - else - p = null; - V pv = (p == null) ? null : p.val; - val = remappingFunction.apply(key, pv); - if (val != null) { - if (p != null) - p.val = val; - else { - delta = 1; - t.putTreeVal(h, key, val); - } - } - else if (p != null) { - delta = -1; - if (t.removeTreeNode(p)) - setTabAt(tab, i, untreeify(t.first)); - } - } - } - } - if (binCount != 0) { - if (binCount >= TREEIFY_THRESHOLD) - treeifyBin(tab, i); - break; - } - } - } - if (delta != 0) - addCount((long)delta, binCount); - return val; - } - - /** - * If the specified key is not already associated with a - * (non-null) value, associates it with the given value. - * Otherwise, replaces the value with the results of the given - * remapping function, or removes if {@code null}. The entire - * method invocation is performed atomically. Some attempted - * update operations on this map by other threads may be blocked - * while computation is in progress, so the computation should be - * short and simple, and must not attempt to update any other - * mappings of this Map. - * - * @param key key with which the specified value is to be associated - * @param value the value to use if absent - * @param remappingFunction the function to recompute a value if present - * @return the new value associated with the specified key, or null if none - * @throws NullPointerException if the specified key or the - * remappingFunction is null - * @throws RuntimeException or Error if the remappingFunction does so, - * in which case the mapping is unchanged - */ - public V merge(K key, V value, BiFun remappingFunction) { - if (key == null || value == null || remappingFunction == null) - throw new NullPointerException(); - int h = spread(key.hashCode()); - V val = null; - int delta = 0; - int binCount = 0; - for (Node[] tab = table;;) { - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0) - tab = initTable(); - else if ((f = tabAt(tab, i = (n - 1) & h)) == null) { - if (casTabAt(tab, i, null, new Node(h, key, value, null))) { - delta = 1; - val = value; - break; - } - } - else if ((fh = f.hash) == MOVED) - tab = helpTransfer(tab, f); - else { - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f, pred = null;; ++binCount) { - K ek; - if (e.hash == h && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { - val = remappingFunction.apply(e.val, value); - if (val != null) - e.val = val; - else { - delta = -1; - Node en = e.next; - if (pred != null) - pred.next = en; - else - setTabAt(tab, i, en); - } - break; - } - pred = e; - if ((e = e.next) == null) { - delta = 1; - val = value; - pred.next = - new Node(h, key, val, null); - break; - } - } - } - else if (f instanceof TreeBin) { - binCount = 2; - TreeBin t = (TreeBin)f; - TreeNode r = t.root; - TreeNode p = (r == null) ? null : - r.findTreeNode(h, key, null); - val = (p == null) ? value : - remappingFunction.apply(p.val, value); - if (val != null) { - if (p != null) - p.val = val; - else { - delta = 1; - t.putTreeVal(h, key, val); - } - } - else if (p != null) { - delta = -1; - if (t.removeTreeNode(p)) - setTabAt(tab, i, untreeify(t.first)); - } - } - } - } - if (binCount != 0) { - if (binCount >= TREEIFY_THRESHOLD) - treeifyBin(tab, i); - break; - } - } - } - if (delta != 0) - addCount((long)delta, binCount); - return val; - } - - // Hashtable legacy methods - - /** - * Legacy method testing if some key maps into the specified value - * in this table. This method is identical in functionality to - * {@link #containsValue(Object)}, and exists solely to ensure - * full compatibility with class {@link java.util.Hashtable}, - * which supported this method prior to introduction of the - * Java Collections framework. - * - * @param value a value to search for - * @return {@code true} if and only if some key maps to the - * {@code value} argument in this table as - * determined by the {@code equals} method; - * {@code false} otherwise - * @throws NullPointerException if the specified value is null - */ - @Deprecated public boolean contains(Object value) { - return containsValue(value); - } - - /** - * Returns an enumeration of the keys in this table. - * - * @return an enumeration of the keys in this table - * @see #keySet() - */ - public Enumeration keys() { - Node[] t; - int f = (t = table) == null ? 0 : t.length; - return new KeyIterator(t, f, 0, f, this); - } - - /** - * Returns an enumeration of the values in this table. - * - * @return an enumeration of the values in this table - * @see #values() - */ - public Enumeration elements() { - Node[] t; - int f = (t = table) == null ? 0 : t.length; - return new ValueIterator(t, f, 0, f, this); - } - - // ConcurrentHashMapV8-only methods - - /** - * Returns the number of mappings. This method should be used - * instead of {@link #size} because a ConcurrentHashMapV8 may - * contain more mappings than can be represented as an int. The - * value returned is an estimate; the actual count may differ if - * there are concurrent insertions or removals. - * - * @return the number of mappings - * @since 1.8 - */ - public long mappingCount() { - long n = sumCount(); - return (n < 0L) ? 0L : n; // ignore transient negative values - } - - /** - * Creates a new {@link Set} backed by a ConcurrentHashMapV8 - * from the given type to {@code Boolean.TRUE}. - * - * @return the new set - * @since 1.8 - */ - public static KeySetView newKeySet() { - return new KeySetView - (new ConcurrentHashMapV8(), Boolean.TRUE); - } - - /** - * Creates a new {@link Set} backed by a ConcurrentHashMapV8 - * from the given type to {@code Boolean.TRUE}. - * - * @param initialCapacity The implementation performs internal - * sizing to accommodate this many elements. - * @throws IllegalArgumentException if the initial capacity of - * elements is negative - * @return the new set - * @since 1.8 - */ - public static KeySetView newKeySet(int initialCapacity) { - return new KeySetView - (new ConcurrentHashMapV8(initialCapacity), Boolean.TRUE); - } - - /** - * Returns a {@link Set} view of the keys in this map, using the - * given common mapped value for any additions (i.e., {@link - * Collection#add} and {@link Collection#addAll(Collection)}). - * This is of course only appropriate if it is acceptable to use - * the same value for all additions from this view. - * - * @param mappedValue the mapped value to use for any additions - * @return the set view - * @throws NullPointerException if the mappedValue is null - */ - public KeySetView keySet(V mappedValue) { - if (mappedValue == null) - throw new NullPointerException(); - return new KeySetView(this, mappedValue); - } - - /* ---------------- Special Nodes -------------- */ - - /** - * A node inserted at head of bins during transfer operations. - */ - static final class ForwardingNode extends Node { - final Node[] nextTable; - ForwardingNode(Node[] tab) { - super(MOVED, null, null, null); - this.nextTable = tab; - } - - Node find(int h, Object k) { - // loop to avoid arbitrarily deep recursion on forwarding nodes - outer: for (Node[] tab = nextTable;;) { - Node e; int n; - if (k == null || tab == null || (n = tab.length) == 0 || - (e = tabAt(tab, (n - 1) & h)) == null) - return null; - for (;;) { - int eh; K ek; - if ((eh = e.hash) == h && - ((ek = e.key) == k || (ek != null && k.equals(ek)))) - return e; - if (eh < 0) { - if (e instanceof ForwardingNode) { - tab = ((ForwardingNode)e).nextTable; - continue outer; - } - else - return e.find(h, k); - } - if ((e = e.next) == null) - return null; - } - } - } - } - - /** - * A place-holder node used in computeIfAbsent and compute - */ - static final class ReservationNode extends Node { - ReservationNode() { - super(RESERVED, null, null, null); - } - - Node find(int h, Object k) { - return null; - } - } - - /* ---------------- Table Initialization and Resizing -------------- */ - - /** - * Initializes table, using the size recorded in sizeCtl. - */ - private final Node[] initTable() { - Node[] tab; int sc; - while ((tab = table) == null || tab.length == 0) { - if ((sc = sizeCtl) < 0) - Thread.yield(); // lost initialization race; just spin - else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { - try { - if ((tab = table) == null || tab.length == 0) { - int n = (sc > 0) ? sc : DEFAULT_CAPACITY; - @SuppressWarnings({"rawtypes","unchecked"}) - Node[] nt = (Node[])new Node[n]; - table = tab = nt; - sc = n - (n >>> 2); - } - } finally { - sizeCtl = sc; - } - break; - } - } - return tab; - } - - /** - * Adds to count, and if table is too small and not already - * resizing, initiates transfer. If already resizing, helps - * perform transfer if work is available. Rechecks occupancy - * after a transfer to see if another resize is already needed - * because resizings are lagging additions. - * - * @param x the count to add - * @param check if <0, don't check resize, if <= 1 only check if uncontended - */ - private final void addCount(long x, int check) { - CounterCell[] as; long b, s; - if ((as = counterCells) != null || - !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { - CounterHashCode hc; CounterCell a; long v; int m; - boolean uncontended = true; - if ((hc = threadCounterHashCode.get()) == null || - as == null || (m = as.length - 1) < 0 || - (a = as[m & hc.code]) == null || - !(uncontended = - U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { - fullAddCount(x, hc, uncontended); - return; - } - if (check <= 1) - return; - s = sumCount(); - } - if (check >= 0) { - Node[] tab, nt; int sc; - while (s >= (long)(sc = sizeCtl) && (tab = table) != null && - tab.length < MAXIMUM_CAPACITY) { - if (sc < 0) { - if (sc == -1 || transferIndex <= transferOrigin || - (nt = nextTable) == null) - break; - if (U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) - transfer(tab, nt); - } - else if (U.compareAndSwapInt(this, SIZECTL, sc, -2)) - transfer(tab, null); - s = sumCount(); - } - } - } - - /** - * Helps transfer if a resize is in progress. - */ - final Node[] helpTransfer(Node[] tab, Node f) { - Node[] nextTab; int sc; - if ((f instanceof ForwardingNode) && - (nextTab = ((ForwardingNode)f).nextTable) != null) { - if (nextTab == nextTable && tab == table && - transferIndex > transferOrigin && (sc = sizeCtl) < -1 && - U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) - transfer(tab, nextTab); - return nextTab; - } - return table; - } - - /** - * Tries to presize table to accommodate the given number of elements. - * - * @param size number of elements (doesn't need to be perfectly accurate) - */ - private final void tryPresize(int size) { - int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : - tableSizeFor(size + (size >>> 1) + 1); - int sc; - while ((sc = sizeCtl) >= 0) { - Node[] tab = table; int n; - if (tab == null || (n = tab.length) == 0) { - n = (sc > c) ? sc : c; - if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { - try { - if (table == tab) { - @SuppressWarnings({"rawtypes","unchecked"}) - Node[] nt = (Node[])new Node[n]; - table = nt; - sc = n - (n >>> 2); - } - } finally { - sizeCtl = sc; - } - } - } - else if (c <= sc || n >= MAXIMUM_CAPACITY) - break; - else if (tab == table && - U.compareAndSwapInt(this, SIZECTL, sc, -2)) - transfer(tab, null); - } - } - - /** - * Moves and/or copies the nodes in each bin to new table. See - * above for explanation. - */ - private final void transfer(Node[] tab, Node[] nextTab) { - int n = tab.length, stride; - if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) - stride = MIN_TRANSFER_STRIDE; // subdivide range - if (nextTab == null) { // initiating - try { - @SuppressWarnings({"rawtypes","unchecked"}) - Node[] nt = (Node[])new Node[n << 1]; - nextTab = nt; - } catch (Throwable ex) { // try to cope with OOME - sizeCtl = Integer.MAX_VALUE; - return; - } - nextTable = nextTab; - transferOrigin = n; - transferIndex = n; - ForwardingNode rev = new ForwardingNode(tab); - for (int k = n; k > 0;) { // progressively reveal ready slots - int nextk = (k > stride) ? k - stride : 0; - for (int m = nextk; m < k; ++m) - nextTab[m] = rev; - for (int m = n + nextk; m < n + k; ++m) - nextTab[m] = rev; - U.putOrderedInt(this, TRANSFERORIGIN, k = nextk); - } - } - int nextn = nextTab.length; - ForwardingNode fwd = new ForwardingNode(nextTab); - boolean advance = true; - boolean finishing = false; // to ensure sweep before committing nextTab - for (int i = 0, bound = 0;;) { - int nextIndex, nextBound, fh; Node f; - while (advance) { - if (--i >= bound || finishing) - advance = false; - else if ((nextIndex = transferIndex) <= transferOrigin) { - i = -1; - advance = false; - } - else if (U.compareAndSwapInt - (this, TRANSFERINDEX, nextIndex, - nextBound = (nextIndex > stride ? - nextIndex - stride : 0))) { - bound = nextBound; - i = nextIndex - 1; - advance = false; - } - } - if (i < 0 || i >= n || i + n >= nextn) { - if (finishing) { - nextTable = null; - table = nextTab; - sizeCtl = (n << 1) - (n >>> 1); - return; - } - for (int sc;;) { - if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, ++sc)) { - if (sc != -1) - return; - finishing = advance = true; - i = n; // recheck before commit - break; - } - } - } - else if ((f = tabAt(tab, i)) == null) { - if (casTabAt(tab, i, null, fwd)) { - setTabAt(nextTab, i, null); - setTabAt(nextTab, i + n, null); - advance = true; - } - } - else if ((fh = f.hash) == MOVED) - advance = true; // already processed - else { - synchronized (f) { - if (tabAt(tab, i) == f) { - Node ln, hn; - if (fh >= 0) { - int runBit = fh & n; - Node lastRun = f; - for (Node p = f.next; p != null; p = p.next) { - int b = p.hash & n; - if (b != runBit) { - runBit = b; - lastRun = p; - } - } - if (runBit == 0) { - ln = lastRun; - hn = null; - } - else { - hn = lastRun; - ln = null; - } - for (Node p = f; p != lastRun; p = p.next) { - int ph = p.hash; K pk = p.key; V pv = p.val; - if ((ph & n) == 0) - ln = new Node(ph, pk, pv, ln); - else - hn = new Node(ph, pk, pv, hn); - } - setTabAt(nextTab, i, ln); - setTabAt(nextTab, i + n, hn); - setTabAt(tab, i, fwd); - advance = true; - } - else if (f instanceof TreeBin) { - TreeBin t = (TreeBin)f; - TreeNode lo = null, loTail = null; - TreeNode hi = null, hiTail = null; - int lc = 0, hc = 0; - for (Node e = t.first; e != null; e = e.next) { - int h = e.hash; - TreeNode p = new TreeNode - (h, e.key, e.val, null, null); - if ((h & n) == 0) { - if ((p.prev = loTail) == null) - lo = p; - else - loTail.next = p; - loTail = p; - ++lc; - } - else { - if ((p.prev = hiTail) == null) - hi = p; - else - hiTail.next = p; - hiTail = p; - ++hc; - } - } - ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : - (hc != 0) ? new TreeBin(lo) : t; - hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : - (lc != 0) ? new TreeBin(hi) : t; - setTabAt(nextTab, i, ln); - setTabAt(nextTab, i + n, hn); - setTabAt(tab, i, fwd); - advance = true; - } - } - } - } - } - } - - /* ---------------- Conversion from/to TreeBins -------------- */ - - /** - * Replaces all linked nodes in bin at given index unless table is - * too small, in which case resizes instead. - */ - private final void treeifyBin(Node[] tab, int index) { - Node b; int n, sc; - if (tab != null) { - if ((n = tab.length) < MIN_TREEIFY_CAPACITY) { - if (tab == table && (sc = sizeCtl) >= 0 && - U.compareAndSwapInt(this, SIZECTL, sc, -2)) - transfer(tab, null); - } - else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { - synchronized (b) { - if (tabAt(tab, index) == b) { - TreeNode hd = null, tl = null; - for (Node e = b; e != null; e = e.next) { - TreeNode p = - new TreeNode(e.hash, e.key, e.val, - null, null); - if ((p.prev = tl) == null) - hd = p; - else - tl.next = p; - tl = p; - } - setTabAt(tab, index, new TreeBin(hd)); - } - } - } - } - } - - /** - * Returns a list on non-TreeNodes replacing those in given list. - */ - static Node untreeify(Node b) { - Node hd = null, tl = null; - for (Node q = b; q != null; q = q.next) { - Node p = new Node(q.hash, q.key, q.val, null); - if (tl == null) - hd = p; - else - tl.next = p; - tl = p; - } - return hd; - } - - /* ---------------- TreeNodes -------------- */ - - /** - * Nodes for use in TreeBins - */ - static final class TreeNode extends Node { - TreeNode parent; // red-black tree links - TreeNode left; - TreeNode right; - TreeNode prev; // needed to unlink next upon deletion - boolean red; - - TreeNode(int hash, K key, V val, Node next, - TreeNode parent) { - super(hash, key, val, next); - this.parent = parent; - } - - Node find(int h, Object k) { - return findTreeNode(h, k, null); - } - - /** - * Returns the TreeNode (or null if not found) for the given key - * starting at given root. - */ - final TreeNode findTreeNode(int h, Object k, Class kc) { - if (k != null) { - TreeNode p = this; - do { - int ph, dir; K pk; TreeNode q; - TreeNode pl = p.left, pr = p.right; - if ((ph = p.hash) > h) - p = pl; - else if (ph < h) - p = pr; - else if ((pk = p.key) == k || (pk != null && k.equals(pk))) - return p; - else if (pl == null && pr == null) - break; - else if ((kc != null || - (kc = comparableClassFor(k)) != null) && - (dir = compareComparables(kc, k, pk)) != 0) - p = (dir < 0) ? pl : pr; - else if (pl == null) - p = pr; - else if (pr == null || - (q = pr.findTreeNode(h, k, kc)) == null) - p = pl; - else - return q; - } while (p != null); - } - return null; - } - } - - /* ---------------- TreeBins -------------- */ - - /** - * TreeNodes used at the heads of bins. TreeBins do not hold user - * keys or values, but instead point to list of TreeNodes and - * their root. They also maintain a parasitic read-write lock - * forcing writers (who hold bin lock) to wait for readers (who do - * not) to complete before tree restructuring operations. - */ - static final class TreeBin extends Node { - TreeNode root; - volatile TreeNode first; - volatile Thread waiter; - volatile int lockState; - // values for lockState - static final int WRITER = 1; // set while holding write lock - static final int WAITER = 2; // set when waiting for write lock - static final int READER = 4; // increment value for setting read lock - - /** - * Creates bin with initial set of nodes headed by b. - */ - TreeBin(TreeNode b) { - super(TREEBIN, null, null, null); - this.first = b; - TreeNode r = null; - for (TreeNode x = b, next; x != null; x = next) { - next = (TreeNode)x.next; - x.left = x.right = null; - if (r == null) { - x.parent = null; - x.red = false; - r = x; - } - else { - Object key = x.key; - int hash = x.hash; - Class kc = null; - for (TreeNode p = r;;) { - int dir, ph; - if ((ph = p.hash) > hash) - dir = -1; - else if (ph < hash) - dir = 1; - else if ((kc != null || - (kc = comparableClassFor(key)) != null)) - dir = compareComparables(kc, key, p.key); - else - dir = 0; - TreeNode xp = p; - if ((p = (dir <= 0) ? p.left : p.right) == null) { - x.parent = xp; - if (dir <= 0) - xp.left = x; - else - xp.right = x; - r = balanceInsertion(r, x); - break; - } - } - } - } - this.root = r; - } - - /** - * Acquires write lock for tree restructuring. - */ - private final void lockRoot() { - if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER)) - contendedLock(); // offload to separate method - } - - /** - * Releases write lock for tree restructuring. - */ - private final void unlockRoot() { - lockState = 0; - } - - /** - * Possibly blocks awaiting root lock. - */ - private final void contendedLock() { - boolean waiting = false; - for (int s;;) { - if (((s = lockState) & WRITER) == 0) { - if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) { - if (waiting) - waiter = null; - return; - } - } - else if ((s & WAITER) == 0) { - if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) { - waiting = true; - waiter = Thread.currentThread(); - } - } - else if (waiting) - LockSupport.park(this); - } - } - - /** - * Returns matching node or null if none. Tries to search - * using tree comparisons from root, but continues linear - * search when lock not available. - */ - final Node find(int h, Object k) { - if (k != null) { - for (Node e = first; e != null; e = e.next) { - int s; K ek; - if (((s = lockState) & (WAITER|WRITER)) != 0) { - if (e.hash == h && - ((ek = e.key) == k || (ek != null && k.equals(ek)))) - return e; - } - else if (U.compareAndSwapInt(this, LOCKSTATE, s, - s + READER)) { - TreeNode r, p; - try { - p = ((r = root) == null ? null : - r.findTreeNode(h, k, null)); - } finally { - Thread w; - int ls; - do {} while (!U.compareAndSwapInt - (this, LOCKSTATE, - ls = lockState, ls - READER)); - if (ls == (READER|WAITER) && (w = waiter) != null) - LockSupport.unpark(w); - } - return p; - } - } - } - return null; - } - - /** - * Finds or adds a node. - * @return null if added - */ - final TreeNode putTreeVal(int h, K k, V v) { - Class kc = null; - for (TreeNode p = root;;) { - int dir, ph; K pk; TreeNode q, pr; - if (p == null) { - first = root = new TreeNode(h, k, v, null, null); - break; - } - else if ((ph = p.hash) > h) - dir = -1; - else if (ph < h) - dir = 1; - else if ((pk = p.key) == k || (pk != null && k.equals(pk))) - return p; - else if ((kc == null && - (kc = comparableClassFor(k)) == null) || - (dir = compareComparables(kc, k, pk)) == 0) { - if (p.left == null) - dir = 1; - else if ((pr = p.right) == null || - (q = pr.findTreeNode(h, k, kc)) == null) - dir = -1; - else - return q; - } - TreeNode xp = p; - if ((p = (dir < 0) ? p.left : p.right) == null) { - TreeNode x, f = first; - first = x = new TreeNode(h, k, v, f, xp); - if (f != null) - f.prev = x; - if (dir < 0) - xp.left = x; - else - xp.right = x; - if (!xp.red) - x.red = true; - else { - lockRoot(); - try { - root = balanceInsertion(root, x); - } finally { - unlockRoot(); - } - } - break; - } - } - assert checkInvariants(root); - return null; - } - - /** - * Removes the given node, that must be present before this - * call. This is messier than typical red-black deletion code - * because we cannot swap the contents of an interior node - * with a leaf successor that is pinned by "next" pointers - * that are accessible independently of lock. So instead we - * swap the tree linkages. - * - * @return true if now too small, so should be untreeified - */ - final boolean removeTreeNode(TreeNode p) { - TreeNode next = (TreeNode)p.next; - TreeNode pred = p.prev; // unlink traversal pointers - TreeNode r, rl; - if (pred == null) - first = next; - else - pred.next = next; - if (next != null) - next.prev = pred; - if (first == null) { - root = null; - return true; - } - if ((r = root) == null || r.right == null || // too small - (rl = r.left) == null || rl.left == null) - return true; - lockRoot(); - try { - TreeNode replacement; - TreeNode pl = p.left; - TreeNode pr = p.right; - if (pl != null && pr != null) { - TreeNode s = pr, sl; - while ((sl = s.left) != null) // find successor - s = sl; - boolean c = s.red; s.red = p.red; p.red = c; // swap colors - TreeNode sr = s.right; - TreeNode pp = p.parent; - if (s == pr) { // p was s's direct parent - p.parent = s; - s.right = p; - } - else { - TreeNode sp = s.parent; - if ((p.parent = sp) != null) { - if (s == sp.left) - sp.left = p; - else - sp.right = p; - } - if ((s.right = pr) != null) - pr.parent = s; - } - p.left = null; - if ((p.right = sr) != null) - sr.parent = p; - if ((s.left = pl) != null) - pl.parent = s; - if ((s.parent = pp) == null) - r = s; - else if (p == pp.left) - pp.left = s; - else - pp.right = s; - if (sr != null) - replacement = sr; - else - replacement = p; - } - else if (pl != null) - replacement = pl; - else if (pr != null) - replacement = pr; - else - replacement = p; - if (replacement != p) { - TreeNode pp = replacement.parent = p.parent; - if (pp == null) - r = replacement; - else if (p == pp.left) - pp.left = replacement; - else - pp.right = replacement; - p.left = p.right = p.parent = null; - } - - root = (p.red) ? r : balanceDeletion(r, replacement); - - if (p == replacement) { // detach pointers - TreeNode pp; - if ((pp = p.parent) != null) { - if (p == pp.left) - pp.left = null; - else if (p == pp.right) - pp.right = null; - p.parent = null; - } - } - } finally { - unlockRoot(); - } - assert checkInvariants(root); - return false; - } - - /* ------------------------------------------------------------ */ - // Red-black tree methods, all adapted from CLR - - static TreeNode rotateLeft(TreeNode root, - TreeNode p) { - TreeNode r, pp, rl; - if (p != null && (r = p.right) != null) { - if ((rl = p.right = r.left) != null) - rl.parent = p; - if ((pp = r.parent = p.parent) == null) - (root = r).red = false; - else if (pp.left == p) - pp.left = r; - else - pp.right = r; - r.left = p; - p.parent = r; - } - return root; - } - - static TreeNode rotateRight(TreeNode root, - TreeNode p) { - TreeNode l, pp, lr; - if (p != null && (l = p.left) != null) { - if ((lr = p.left = l.right) != null) - lr.parent = p; - if ((pp = l.parent = p.parent) == null) - (root = l).red = false; - else if (pp.right == p) - pp.right = l; - else - pp.left = l; - l.right = p; - p.parent = l; - } - return root; - } - - static TreeNode balanceInsertion(TreeNode root, - TreeNode x) { - x.red = true; - for (TreeNode xp, xpp, xppl, xppr;;) { - if ((xp = x.parent) == null) { - x.red = false; - return x; - } - else if (!xp.red || (xpp = xp.parent) == null) - return root; - if (xp == (xppl = xpp.left)) { - if ((xppr = xpp.right) != null && xppr.red) { - xppr.red = false; - xp.red = false; - xpp.red = true; - x = xpp; - } - else { - if (x == xp.right) { - root = rotateLeft(root, x = xp); - xpp = (xp = x.parent) == null ? null : xp.parent; - } - if (xp != null) { - xp.red = false; - if (xpp != null) { - xpp.red = true; - root = rotateRight(root, xpp); - } - } - } - } - else { - if (xppl != null && xppl.red) { - xppl.red = false; - xp.red = false; - xpp.red = true; - x = xpp; - } - else { - if (x == xp.left) { - root = rotateRight(root, x = xp); - xpp = (xp = x.parent) == null ? null : xp.parent; - } - if (xp != null) { - xp.red = false; - if (xpp != null) { - xpp.red = true; - root = rotateLeft(root, xpp); - } - } - } - } - } - } - - static TreeNode balanceDeletion(TreeNode root, - TreeNode x) { - for (TreeNode xp, xpl, xpr;;) { - if (x == null || x == root) - return root; - else if ((xp = x.parent) == null) { - x.red = false; - return x; - } - else if (x.red) { - x.red = false; - return root; - } - else if ((xpl = xp.left) == x) { - if ((xpr = xp.right) != null && xpr.red) { - xpr.red = false; - xp.red = true; - root = rotateLeft(root, xp); - xpr = (xp = x.parent) == null ? null : xp.right; - } - if (xpr == null) - x = xp; - else { - TreeNode sl = xpr.left, sr = xpr.right; - if ((sr == null || !sr.red) && - (sl == null || !sl.red)) { - xpr.red = true; - x = xp; - } - else { - if (sr == null || !sr.red) { - if (sl != null) - sl.red = false; - xpr.red = true; - root = rotateRight(root, xpr); - xpr = (xp = x.parent) == null ? - null : xp.right; - } - if (xpr != null) { - xpr.red = (xp == null) ? false : xp.red; - if ((sr = xpr.right) != null) - sr.red = false; - } - if (xp != null) { - xp.red = false; - root = rotateLeft(root, xp); - } - x = root; - } - } - } - else { // symmetric - if (xpl != null && xpl.red) { - xpl.red = false; - xp.red = true; - root = rotateRight(root, xp); - xpl = (xp = x.parent) == null ? null : xp.left; - } - if (xpl == null) - x = xp; - else { - TreeNode sl = xpl.left, sr = xpl.right; - if ((sl == null || !sl.red) && - (sr == null || !sr.red)) { - xpl.red = true; - x = xp; - } - else { - if (sl == null || !sl.red) { - if (sr != null) - sr.red = false; - xpl.red = true; - root = rotateLeft(root, xpl); - xpl = (xp = x.parent) == null ? - null : xp.left; - } - if (xpl != null) { - xpl.red = (xp == null) ? false : xp.red; - if ((sl = xpl.left) != null) - sl.red = false; - } - if (xp != null) { - xp.red = false; - root = rotateRight(root, xp); - } - x = root; - } - } - } - } - } - - /** - * Recursive invariant check - */ - static boolean checkInvariants(TreeNode t) { - TreeNode tp = t.parent, tl = t.left, tr = t.right, - tb = t.prev, tn = (TreeNode)t.next; - if (tb != null && tb.next != t) - return false; - if (tn != null && tn.prev != t) - return false; - if (tp != null && t != tp.left && t != tp.right) - return false; - if (tl != null && (tl.parent != t || tl.hash > t.hash)) - return false; - if (tr != null && (tr.parent != t || tr.hash < t.hash)) - return false; - if (t.red && tl != null && tl.red && tr != null && tr.red) - return false; - if (tl != null && !checkInvariants(tl)) - return false; - if (tr != null && !checkInvariants(tr)) - return false; - return true; - } - - private static final sun.misc.Unsafe U; - private static final long LOCKSTATE; - static { - try { - U = getUnsafe(); - Class k = TreeBin.class; - LOCKSTATE = U.objectFieldOffset - (k.getDeclaredField("lockState")); - } catch (Exception e) { - throw new Error(e); - } - } - } - - /* ----------------Table Traversal -------------- */ - - /** - * Encapsulates traversal for methods such as containsValue; also - * serves as a base class for other iterators and spliterators. - * - * Method advance visits once each still-valid node that was - * reachable upon iterator construction. It might miss some that - * were added to a bin after the bin was visited, which is OK wrt - * consistency guarantees. Maintaining this property in the face - * of possible ongoing resizes requires a fair amount of - * bookkeeping state that is difficult to optimize away amidst - * volatile accesses. Even so, traversal maintains reasonable - * throughput. - * - * Normally, iteration proceeds bin-by-bin traversing lists. - * However, if the table has been resized, then all future steps - * must traverse both the bin at the current index as well as at - * (index + baseSize); and so on for further resizings. To - * paranoically cope with potential sharing by users of iterators - * across threads, iteration terminates if a bounds checks fails - * for a table read. - */ - static class Traverser { - Node[] tab; // current table; updated if resized - Node next; // the next entry to use - int index; // index of bin to use next - int baseIndex; // current index of initial table - int baseLimit; // index bound for initial table - final int baseSize; // initial table size - - Traverser(Node[] tab, int size, int index, int limit) { - this.tab = tab; - this.baseSize = size; - this.baseIndex = this.index = index; - this.baseLimit = limit; - this.next = null; - } - - /** - * Advances if possible, returning next valid node, or null if none. - */ - final Node advance() { - Node e; - if ((e = next) != null) - e = e.next; - for (;;) { - Node[] t; int i, n; K ek; // must use locals in checks - if (e != null) - return next = e; - if (baseIndex >= baseLimit || (t = tab) == null || - (n = t.length) <= (i = index) || i < 0) - return next = null; - if ((e = tabAt(t, index)) != null && e.hash < 0) { - if (e instanceof ForwardingNode) { - tab = ((ForwardingNode)e).nextTable; - e = null; - continue; - } - else if (e instanceof TreeBin) - e = ((TreeBin)e).first; - else - e = null; - } - if ((index += baseSize) >= n) - index = ++baseIndex; // visit upper slots if present - } - } - } - - /** - * Base of key, value, and entry Iterators. Adds fields to - * Traverser to support iterator.remove. - */ - static class BaseIterator extends Traverser { - final ConcurrentHashMapV8 map; - Node lastReturned; - BaseIterator(Node[] tab, int size, int index, int limit, - ConcurrentHashMapV8 map) { - super(tab, size, index, limit); - this.map = map; - advance(); - } - - public final boolean hasNext() { return next != null; } - public final boolean hasMoreElements() { return next != null; } - - public final void remove() { - Node p; - if ((p = lastReturned) == null) - throw new IllegalStateException(); - lastReturned = null; - map.replaceNode(p.key, null, null); - } - } - - static final class KeyIterator extends BaseIterator - implements Iterator, Enumeration { - KeyIterator(Node[] tab, int index, int size, int limit, - ConcurrentHashMapV8 map) { - super(tab, index, size, limit, map); - } - - public final K next() { - Node p; - if ((p = next) == null) - throw new NoSuchElementException(); - K k = p.key; - lastReturned = p; - advance(); - return k; - } - - public final K nextElement() { return next(); } - } - - static final class ValueIterator extends BaseIterator - implements Iterator, Enumeration { - ValueIterator(Node[] tab, int index, int size, int limit, - ConcurrentHashMapV8 map) { - super(tab, index, size, limit, map); - } - - public final V next() { - Node p; - if ((p = next) == null) - throw new NoSuchElementException(); - V v = p.val; - lastReturned = p; - advance(); - return v; - } - - public final V nextElement() { return next(); } - } - - static final class EntryIterator extends BaseIterator - implements Iterator> { - EntryIterator(Node[] tab, int index, int size, int limit, - ConcurrentHashMapV8 map) { - super(tab, index, size, limit, map); - } - - public final Map.Entry next() { - Node p; - if ((p = next) == null) - throw new NoSuchElementException(); - K k = p.key; - V v = p.val; - lastReturned = p; - advance(); - return new MapEntry(k, v, map); - } - } - - /** - * Exported Entry for EntryIterator - */ - static final class MapEntry implements Map.Entry { - final K key; // non-null - V val; // non-null - final ConcurrentHashMapV8 map; - MapEntry(K key, V val, ConcurrentHashMapV8 map) { - this.key = key; - this.val = val; - this.map = map; - } - public K getKey() { return key; } - public V getValue() { return val; } - public int hashCode() { return key.hashCode() ^ val.hashCode(); } - public String toString() { return key + "=" + val; } - - public boolean equals(Object o) { - Object k, v; Map.Entry e; - return ((o instanceof Map.Entry) && - (k = (e = (Map.Entry)o).getKey()) != null && - (v = e.getValue()) != null && - (k == key || k.equals(key)) && - (v == val || v.equals(val))); - } - - /** - * Sets our entry's value and writes through to the map. The - * value to return is somewhat arbitrary here. Since we do not - * necessarily track asynchronous changes, the most recent - * "previous" value could be different from what we return (or - * could even have been removed, in which case the put will - * re-establish). We do not and cannot guarantee more. - */ - public V setValue(V value) { - if (value == null) throw new NullPointerException(); - V v = val; - val = value; - map.put(key, value); - return v; - } - } - - static final class KeySpliterator extends Traverser - implements ConcurrentHashMapSpliterator { - long est; // size estimate - KeySpliterator(Node[] tab, int size, int index, int limit, - long est) { - super(tab, size, index, limit); - this.est = est; - } - - public ConcurrentHashMapSpliterator trySplit() { - int i, f, h; - return (h = ((i = baseIndex) + (f = baseLimit)) >>> 1) <= i ? null : - new KeySpliterator(tab, baseSize, baseLimit = h, - f, est >>>= 1); - } - - public void forEachRemaining(Action action) { - if (action == null) throw new NullPointerException(); - for (Node p; (p = advance()) != null;) - action.apply(p.key); - } - - public boolean tryAdvance(Action action) { - if (action == null) throw new NullPointerException(); - Node p; - if ((p = advance()) == null) - return false; - action.apply(p.key); - return true; - } - - public long estimateSize() { return est; } - - } - - static final class ValueSpliterator extends Traverser - implements ConcurrentHashMapSpliterator { - long est; // size estimate - ValueSpliterator(Node[] tab, int size, int index, int limit, - long est) { - super(tab, size, index, limit); - this.est = est; - } - - public ConcurrentHashMapSpliterator trySplit() { - int i, f, h; - return (h = ((i = baseIndex) + (f = baseLimit)) >>> 1) <= i ? null : - new ValueSpliterator(tab, baseSize, baseLimit = h, - f, est >>>= 1); - } - - public void forEachRemaining(Action action) { - if (action == null) throw new NullPointerException(); - for (Node p; (p = advance()) != null;) - action.apply(p.val); - } - - public boolean tryAdvance(Action action) { - if (action == null) throw new NullPointerException(); - Node p; - if ((p = advance()) == null) - return false; - action.apply(p.val); - return true; - } - - public long estimateSize() { return est; } - - } - - static final class EntrySpliterator extends Traverser - implements ConcurrentHashMapSpliterator> { - final ConcurrentHashMapV8 map; // To export MapEntry - long est; // size estimate - EntrySpliterator(Node[] tab, int size, int index, int limit, - long est, ConcurrentHashMapV8 map) { - super(tab, size, index, limit); - this.map = map; - this.est = est; - } - - public ConcurrentHashMapSpliterator> trySplit() { - int i, f, h; - return (h = ((i = baseIndex) + (f = baseLimit)) >>> 1) <= i ? null : - new EntrySpliterator(tab, baseSize, baseLimit = h, - f, est >>>= 1, map); - } - - public void forEachRemaining(Action> action) { - if (action == null) throw new NullPointerException(); - for (Node p; (p = advance()) != null; ) - action.apply(new MapEntry(p.key, p.val, map)); - } - - public boolean tryAdvance(Action> action) { - if (action == null) throw new NullPointerException(); - Node p; - if ((p = advance()) == null) - return false; - action.apply(new MapEntry(p.key, p.val, map)); - return true; - } - - public long estimateSize() { return est; } - - } - - // Parallel bulk operations - - /** - * Computes initial batch value for bulk tasks. The returned value - * is approximately exp2 of the number of times (minus one) to - * split task by two before executing leaf action. This value is - * faster to compute and more convenient to use as a guide to - * splitting than is the depth, since it is used while dividing by - * two anyway. - */ - final int batchFor(long b) { - long n; - if (b == Long.MAX_VALUE || (n = sumCount()) <= 1L || n < b) - return 0; - int sp = ForkJoinPool.getCommonPoolParallelism() << 2; // slack of 4 - return (b <= 0L || (n /= b) >= sp) ? sp : (int)n; - } - - /** - * Performs the given action for each (key, value). - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param action the action - * @since 1.8 - */ - public void forEach(long parallelismThreshold, - BiAction action) { - if (action == null) throw new NullPointerException(); - new ForEachMappingTask - (null, batchFor(parallelismThreshold), 0, 0, table, - action).invoke(); - } - - /** - * Performs the given action for each non-null transformation - * of each (key, value). - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element, or null if there is no transformation (in - * which case the action is not applied) - * @param action the action - * @since 1.8 - */ - public void forEach(long parallelismThreshold, - BiFun transformer, - Action action) { - if (transformer == null || action == null) - throw new NullPointerException(); - new ForEachTransformedMappingTask - (null, batchFor(parallelismThreshold), 0, 0, table, - transformer, action).invoke(); - } - - /** - * Returns a non-null result from applying the given search - * function on each (key, value), or null if none. Upon - * success, further element processing is suppressed and the - * results of any other parallel invocations of the search - * function are ignored. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param searchFunction a function returning a non-null - * result on success, else null - * @return a non-null result from applying the given search - * function on each (key, value), or null if none - * @since 1.8 - */ - public U search(long parallelismThreshold, - BiFun searchFunction) { - if (searchFunction == null) throw new NullPointerException(); - return new SearchMappingsTask - (null, batchFor(parallelismThreshold), 0, 0, table, - searchFunction, new AtomicReference()).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all (key, value) pairs using the given reducer to - * combine values, or null if none. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element, or null if there is no transformation (in - * which case it is not combined) - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all (key, value) pairs - * @since 1.8 - */ - public U reduce(long parallelismThreshold, - BiFun transformer, - BiFun reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceMappingsTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all (key, value) pairs using the given reducer to - * combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all (key, value) pairs - * @since 1.8 - */ - public double reduceToDouble(long parallelismThreshold, - ObjectByObjectToDouble transformer, - double basis, - DoubleByDoubleToDouble reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceMappingsToDoubleTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all (key, value) pairs using the given reducer to - * combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all (key, value) pairs - * @since 1.8 - */ - public long reduceToLong(long parallelismThreshold, - ObjectByObjectToLong transformer, - long basis, - LongByLongToLong reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceMappingsToLongTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all (key, value) pairs using the given reducer to - * combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all (key, value) pairs - * @since 1.8 - */ - public int reduceToInt(long parallelismThreshold, - ObjectByObjectToInt transformer, - int basis, - IntByIntToInt reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceMappingsToIntTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Performs the given action for each key. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param action the action - * @since 1.8 - */ - public void forEachKey(long parallelismThreshold, - Action action) { - if (action == null) throw new NullPointerException(); - new ForEachKeyTask - (null, batchFor(parallelismThreshold), 0, 0, table, - action).invoke(); - } - - /** - * Performs the given action for each non-null transformation - * of each key. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element, or null if there is no transformation (in - * which case the action is not applied) - * @param action the action - * @since 1.8 - */ - public void forEachKey(long parallelismThreshold, - Fun transformer, - Action action) { - if (transformer == null || action == null) - throw new NullPointerException(); - new ForEachTransformedKeyTask - (null, batchFor(parallelismThreshold), 0, 0, table, - transformer, action).invoke(); - } - - /** - * Returns a non-null result from applying the given search - * function on each key, or null if none. Upon success, - * further element processing is suppressed and the results of - * any other parallel invocations of the search function are - * ignored. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param searchFunction a function returning a non-null - * result on success, else null - * @return a non-null result from applying the given search - * function on each key, or null if none - * @since 1.8 - */ - public U searchKeys(long parallelismThreshold, - Fun searchFunction) { - if (searchFunction == null) throw new NullPointerException(); - return new SearchKeysTask - (null, batchFor(parallelismThreshold), 0, 0, table, - searchFunction, new AtomicReference()).invoke(); - } - - /** - * Returns the result of accumulating all keys using the given - * reducer to combine values, or null if none. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param reducer a commutative associative combining function - * @return the result of accumulating all keys using the given - * reducer to combine values, or null if none - * @since 1.8 - */ - public K reduceKeys(long parallelismThreshold, - BiFun reducer) { - if (reducer == null) throw new NullPointerException(); - return new ReduceKeysTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all keys using the given reducer to combine values, or - * null if none. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element, or null if there is no transformation (in - * which case it is not combined) - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all keys - * @since 1.8 - */ - public U reduceKeys(long parallelismThreshold, - Fun transformer, - BiFun reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceKeysTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all keys using the given reducer to combine values, and - * the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all keys - * @since 1.8 - */ - public double reduceKeysToDouble(long parallelismThreshold, - ObjectToDouble transformer, - double basis, - DoubleByDoubleToDouble reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceKeysToDoubleTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all keys using the given reducer to combine values, and - * the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all keys - * @since 1.8 - */ - public long reduceKeysToLong(long parallelismThreshold, - ObjectToLong transformer, - long basis, - LongByLongToLong reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceKeysToLongTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all keys using the given reducer to combine values, and - * the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all keys - * @since 1.8 - */ - public int reduceKeysToInt(long parallelismThreshold, - ObjectToInt transformer, - int basis, - IntByIntToInt reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceKeysToIntTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Performs the given action for each value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param action the action - * @since 1.8 - */ - public void forEachValue(long parallelismThreshold, - Action action) { - if (action == null) - throw new NullPointerException(); - new ForEachValueTask - (null, batchFor(parallelismThreshold), 0, 0, table, - action).invoke(); - } - - /** - * Performs the given action for each non-null transformation - * of each value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element, or null if there is no transformation (in - * which case the action is not applied) - * @param action the action - * @since 1.8 - */ - public void forEachValue(long parallelismThreshold, - Fun transformer, - Action action) { - if (transformer == null || action == null) - throw new NullPointerException(); - new ForEachTransformedValueTask - (null, batchFor(parallelismThreshold), 0, 0, table, - transformer, action).invoke(); - } - - /** - * Returns a non-null result from applying the given search - * function on each value, or null if none. Upon success, - * further element processing is suppressed and the results of - * any other parallel invocations of the search function are - * ignored. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param searchFunction a function returning a non-null - * result on success, else null - * @return a non-null result from applying the given search - * function on each value, or null if none - * @since 1.8 - */ - public U searchValues(long parallelismThreshold, - Fun searchFunction) { - if (searchFunction == null) throw new NullPointerException(); - return new SearchValuesTask - (null, batchFor(parallelismThreshold), 0, 0, table, - searchFunction, new AtomicReference()).invoke(); - } - - /** - * Returns the result of accumulating all values using the - * given reducer to combine values, or null if none. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param reducer a commutative associative combining function - * @return the result of accumulating all values - * @since 1.8 - */ - public V reduceValues(long parallelismThreshold, - BiFun reducer) { - if (reducer == null) throw new NullPointerException(); - return new ReduceValuesTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all values using the given reducer to combine values, or - * null if none. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element, or null if there is no transformation (in - * which case it is not combined) - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all values - * @since 1.8 - */ - public U reduceValues(long parallelismThreshold, - Fun transformer, - BiFun reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceValuesTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all values using the given reducer to combine values, - * and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all values - * @since 1.8 - */ - public double reduceValuesToDouble(long parallelismThreshold, - ObjectToDouble transformer, - double basis, - DoubleByDoubleToDouble reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceValuesToDoubleTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all values using the given reducer to combine values, - * and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all values - * @since 1.8 - */ - public long reduceValuesToLong(long parallelismThreshold, - ObjectToLong transformer, - long basis, - LongByLongToLong reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceValuesToLongTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all values using the given reducer to combine values, - * and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all values - * @since 1.8 - */ - public int reduceValuesToInt(long parallelismThreshold, - ObjectToInt transformer, - int basis, - IntByIntToInt reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceValuesToIntTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Performs the given action for each entry. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param action the action - * @since 1.8 - */ - public void forEachEntry(long parallelismThreshold, - Action> action) { - if (action == null) throw new NullPointerException(); - new ForEachEntryTask(null, batchFor(parallelismThreshold), 0, 0, table, - action).invoke(); - } - - /** - * Performs the given action for each non-null transformation - * of each entry. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element, or null if there is no transformation (in - * which case the action is not applied) - * @param action the action - * @since 1.8 - */ - public void forEachEntry(long parallelismThreshold, - Fun, ? extends U> transformer, - Action action) { - if (transformer == null || action == null) - throw new NullPointerException(); - new ForEachTransformedEntryTask - (null, batchFor(parallelismThreshold), 0, 0, table, - transformer, action).invoke(); - } - - /** - * Returns a non-null result from applying the given search - * function on each entry, or null if none. Upon success, - * further element processing is suppressed and the results of - * any other parallel invocations of the search function are - * ignored. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param searchFunction a function returning a non-null - * result on success, else null - * @return a non-null result from applying the given search - * function on each entry, or null if none - * @since 1.8 - */ - public U searchEntries(long parallelismThreshold, - Fun, ? extends U> searchFunction) { - if (searchFunction == null) throw new NullPointerException(); - return new SearchEntriesTask - (null, batchFor(parallelismThreshold), 0, 0, table, - searchFunction, new AtomicReference()).invoke(); - } - - /** - * Returns the result of accumulating all entries using the - * given reducer to combine values, or null if none. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param reducer a commutative associative combining function - * @return the result of accumulating all entries - * @since 1.8 - */ - public Map.Entry reduceEntries(long parallelismThreshold, - BiFun, Map.Entry, ? extends Map.Entry> reducer) { - if (reducer == null) throw new NullPointerException(); - return new ReduceEntriesTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all entries using the given reducer to combine values, - * or null if none. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element, or null if there is no transformation (in - * which case it is not combined) - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all entries - * @since 1.8 - */ - public U reduceEntries(long parallelismThreshold, - Fun, ? extends U> transformer, - BiFun reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceEntriesTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all entries using the given reducer to combine values, - * and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all entries - * @since 1.8 - */ - public double reduceEntriesToDouble(long parallelismThreshold, - ObjectToDouble> transformer, - double basis, - DoubleByDoubleToDouble reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceEntriesToDoubleTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all entries using the given reducer to combine values, - * and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all entries - * @since 1.8 - */ - public long reduceEntriesToLong(long parallelismThreshold, - ObjectToLong> transformer, - long basis, - LongByLongToLong reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceEntriesToLongTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation - * of all entries using the given reducer to combine values, - * and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements - * needed for this operation to be executed in parallel - * @param transformer a function returning the transformation - * for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation - * of all entries - * @since 1.8 - */ - public int reduceEntriesToInt(long parallelismThreshold, - ObjectToInt> transformer, - int basis, - IntByIntToInt reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceEntriesToIntTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - - /* ----------------Views -------------- */ - - /** - * Base class for views. - */ - abstract static class CollectionView - implements Collection, java.io.Serializable { - private static final long serialVersionUID = 7249069246763182397L; - final ConcurrentHashMapV8 map; - CollectionView(ConcurrentHashMapV8 map) { this.map = map; } - - /** - * Returns the map backing this view. - * - * @return the map backing this view - */ - public ConcurrentHashMapV8 getMap() { return map; } - - /** - * Removes all of the elements from this view, by removing all - * the mappings from the map backing this view. - */ - public final void clear() { map.clear(); } - public final int size() { return map.size(); } - public final boolean isEmpty() { return map.isEmpty(); } - - // implementations below rely on concrete classes supplying these - // abstract methods - /** - * Returns a "weakly consistent" iterator that will never - * throw {@link ConcurrentModificationException}, and - * guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not - * guaranteed to) reflect any modifications subsequent to - * construction. - */ - public abstract Iterator iterator(); - public abstract boolean contains(Object o); - public abstract boolean remove(Object o); - - private static final String oomeMsg = "Required array size too large"; - - public final Object[] toArray() { - long sz = map.mappingCount(); - if (sz > MAX_ARRAY_SIZE) - throw new OutOfMemoryError(oomeMsg); - int n = (int)sz; - Object[] r = new Object[n]; - int i = 0; - for (E e : this) { - if (i == n) { - if (n >= MAX_ARRAY_SIZE) - throw new OutOfMemoryError(oomeMsg); - if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) - n = MAX_ARRAY_SIZE; - else - n += (n >>> 1) + 1; - r = Arrays.copyOf(r, n); - } - r[i++] = e; - } - return (i == n) ? r : Arrays.copyOf(r, i); - } - - @SuppressWarnings("unchecked") - public final T[] toArray(T[] a) { - long sz = map.mappingCount(); - if (sz > MAX_ARRAY_SIZE) - throw new OutOfMemoryError(oomeMsg); - int m = (int)sz; - T[] r = (a.length >= m) ? a : - (T[])java.lang.reflect.Array - .newInstance(a.getClass().getComponentType(), m); - int n = r.length; - int i = 0; - for (E e : this) { - if (i == n) { - if (n >= MAX_ARRAY_SIZE) - throw new OutOfMemoryError(oomeMsg); - if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) - n = MAX_ARRAY_SIZE; - else - n += (n >>> 1) + 1; - r = Arrays.copyOf(r, n); - } - r[i++] = (T)e; - } - if (a == r && i < n) { - r[i] = null; // null-terminate - return r; - } - return (i == n) ? r : Arrays.copyOf(r, i); - } - - /** - * Returns a string representation of this collection. - * The string representation consists of the string representations - * of the collection's elements in the order they are returned by - * its iterator, enclosed in square brackets ({@code "[]"}). - * Adjacent elements are separated by the characters {@code ", "} - * (comma and space). Elements are converted to strings as by - * {@link String#valueOf(Object)}. - * - * @return a string representation of this collection - */ - public final String toString() { - StringBuilder sb = new StringBuilder(); - sb.append('['); - Iterator it = iterator(); - if (it.hasNext()) { - for (;;) { - Object e = it.next(); - sb.append(e == this ? "(this Collection)" : e); - if (!it.hasNext()) - break; - sb.append(',').append(' '); - } - } - return sb.append(']').toString(); - } - - public final boolean containsAll(Collection c) { - if (c != this) { - for (Object e : c) { - if (e == null || !contains(e)) - return false; - } - } - return true; - } - - public final boolean removeAll(Collection c) { - boolean modified = false; - for (Iterator it = iterator(); it.hasNext();) { - if (c.contains(it.next())) { - it.remove(); - modified = true; - } - } - return modified; - } - - public final boolean retainAll(Collection c) { - boolean modified = false; - for (Iterator it = iterator(); it.hasNext();) { - if (!c.contains(it.next())) { - it.remove(); - modified = true; - } - } - return modified; - } - - } - - /** - * A view of a ConcurrentHashMapV8 as a {@link Set} of keys, in - * which additions may optionally be enabled by mapping to a - * common value. This class cannot be directly instantiated. - * See {@link #keySet() keySet()}, - * {@link #keySet(Object) keySet(V)}, - * {@link #newKeySet() newKeySet()}, - * {@link #newKeySet(int) newKeySet(int)}. - * - * @since 1.8 - */ - public static class KeySetView extends CollectionView - implements Set, java.io.Serializable { - private static final long serialVersionUID = 7249069246763182397L; - private final V value; - KeySetView(ConcurrentHashMapV8 map, V value) { // non-public - super(map); - this.value = value; - } - - /** - * Returns the default mapped value for additions, - * or {@code null} if additions are not supported. - * - * @return the default mapped value for additions, or {@code null} - * if not supported - */ - public V getMappedValue() { return value; } - - /** - * {@inheritDoc} - * @throws NullPointerException if the specified key is null - */ - public boolean contains(Object o) { return map.containsKey(o); } - - /** - * Removes the key from this map view, by removing the key (and its - * corresponding value) from the backing map. This method does - * nothing if the key is not in the map. - * - * @param o the key to be removed from the backing map - * @return {@code true} if the backing map contained the specified key - * @throws NullPointerException if the specified key is null - */ - public boolean remove(Object o) { return map.remove(o) != null; } - - /** - * @return an iterator over the keys of the backing map - */ - public Iterator iterator() { - Node[] t; - ConcurrentHashMapV8 m = map; - int f = (t = m.table) == null ? 0 : t.length; - return new KeyIterator(t, f, 0, f, m); - } - - /** - * Adds the specified key to this set view by mapping the key to - * the default mapped value in the backing map, if defined. - * - * @param e key to be added - * @return {@code true} if this set changed as a result of the call - * @throws NullPointerException if the specified key is null - * @throws UnsupportedOperationException if no default mapped value - * for additions was provided - */ - public boolean add(K e) { - V v; - if ((v = value) == null) - throw new UnsupportedOperationException(); - return map.putVal(e, v, true) == null; - } - - /** - * Adds all of the elements in the specified collection to this set, - * as if by calling {@link #add} on each one. - * - * @param c the elements to be inserted into this set - * @return {@code true} if this set changed as a result of the call - * @throws NullPointerException if the collection or any of its - * elements are {@code null} - * @throws UnsupportedOperationException if no default mapped value - * for additions was provided - */ - public boolean addAll(Collection c) { - boolean added = false; - V v; - if ((v = value) == null) - throw new UnsupportedOperationException(); - for (K e : c) { - if (map.putVal(e, v, true) == null) - added = true; - } - return added; - } - - public int hashCode() { - int h = 0; - for (K e : this) - h += e.hashCode(); - return h; - } - - public boolean equals(Object o) { - Set c; - return ((o instanceof Set) && - ((c = (Set)o) == this || - (containsAll(c) && c.containsAll(this)))); - } - - public ConcurrentHashMapSpliterator spliterator166() { - Node[] t; - ConcurrentHashMapV8 m = map; - long n = m.sumCount(); - int f = (t = m.table) == null ? 0 : t.length; - return new KeySpliterator(t, f, 0, f, n < 0L ? 0L : n); - } - - public void forEach(Action action) { - if (action == null) throw new NullPointerException(); - Node[] t; - if ((t = map.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) - action.apply(p.key); - } - } - } - - /** - * A view of a ConcurrentHashMapV8 as a {@link Collection} of - * values, in which additions are disabled. This class cannot be - * directly instantiated. See {@link #values()}. - */ - static final class ValuesView extends CollectionView - implements Collection, java.io.Serializable { - private static final long serialVersionUID = 2249069246763182397L; - ValuesView(ConcurrentHashMapV8 map) { super(map); } - public final boolean contains(Object o) { - return map.containsValue(o); - } - - public final boolean remove(Object o) { - if (o != null) { - for (Iterator it = iterator(); it.hasNext();) { - if (o.equals(it.next())) { - it.remove(); - return true; - } - } - } - return false; - } - - public final Iterator iterator() { - ConcurrentHashMapV8 m = map; - Node[] t; - int f = (t = m.table) == null ? 0 : t.length; - return new ValueIterator(t, f, 0, f, m); - } - - public final boolean add(V e) { - throw new UnsupportedOperationException(); - } - public final boolean addAll(Collection c) { - throw new UnsupportedOperationException(); - } - - public ConcurrentHashMapSpliterator spliterator166() { - Node[] t; - ConcurrentHashMapV8 m = map; - long n = m.sumCount(); - int f = (t = m.table) == null ? 0 : t.length; - return new ValueSpliterator(t, f, 0, f, n < 0L ? 0L : n); - } - - public void forEach(Action action) { - if (action == null) throw new NullPointerException(); - Node[] t; - if ((t = map.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) - action.apply(p.val); - } - } - } - - /** - * A view of a ConcurrentHashMapV8 as a {@link Set} of (key, value) - * entries. This class cannot be directly instantiated. See - * {@link #entrySet()}. - */ - static final class EntrySetView extends CollectionView> - implements Set>, java.io.Serializable { - private static final long serialVersionUID = 2249069246763182397L; - EntrySetView(ConcurrentHashMapV8 map) { super(map); } - - public boolean contains(Object o) { - Object k, v, r; Map.Entry e; - return ((o instanceof Map.Entry) && - (k = (e = (Map.Entry)o).getKey()) != null && - (r = map.get(k)) != null && - (v = e.getValue()) != null && - (v == r || v.equals(r))); - } - - public boolean remove(Object o) { - Object k, v; Map.Entry e; - return ((o instanceof Map.Entry) && - (k = (e = (Map.Entry)o).getKey()) != null && - (v = e.getValue()) != null && - map.remove(k, v)); - } - - /** - * @return an iterator over the entries of the backing map - */ - public Iterator> iterator() { - ConcurrentHashMapV8 m = map; - Node[] t; - int f = (t = m.table) == null ? 0 : t.length; - return new EntryIterator(t, f, 0, f, m); - } - - public boolean add(Entry e) { - return map.putVal(e.getKey(), e.getValue(), false) == null; - } - - public boolean addAll(Collection> c) { - boolean added = false; - for (Entry e : c) { - if (add(e)) - added = true; - } - return added; - } - - public final int hashCode() { - int h = 0; - Node[] t; - if ((t = map.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - h += p.hashCode(); - } - } - return h; - } - - public final boolean equals(Object o) { - Set c; - return ((o instanceof Set) && - ((c = (Set)o) == this || - (containsAll(c) && c.containsAll(this)))); - } - - public ConcurrentHashMapSpliterator> spliterator166() { - Node[] t; - ConcurrentHashMapV8 m = map; - long n = m.sumCount(); - int f = (t = m.table) == null ? 0 : t.length; - return new EntrySpliterator(t, f, 0, f, n < 0L ? 0L : n, m); - } - - public void forEach(Action> action) { - if (action == null) throw new NullPointerException(); - Node[] t; - if ((t = map.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) - action.apply(new MapEntry(p.key, p.val, map)); - } - } - - } - - // ------------------------------------------------------- - - /** - * Base class for bulk tasks. Repeats some fields and code from - * class Traverser, because we need to subclass CountedCompleter. - */ - abstract static class BulkTask extends CountedCompleter { - Node[] tab; // same as Traverser - Node next; - int index; - int baseIndex; - int baseLimit; - final int baseSize; - int batch; // split control - - BulkTask(BulkTask par, int b, int i, int f, Node[] t) { - super(par); - this.batch = b; - this.index = this.baseIndex = i; - if ((this.tab = t) == null) - this.baseSize = this.baseLimit = 0; - else if (par == null) - this.baseSize = this.baseLimit = t.length; - else { - this.baseLimit = f; - this.baseSize = par.baseSize; - } - } - - /** - * Same as Traverser version - */ - final Node advance() { - Node e; - if ((e = next) != null) - e = e.next; - for (;;) { - Node[] t; int i, n; K ek; // must use locals in checks - if (e != null) - return next = e; - if (baseIndex >= baseLimit || (t = tab) == null || - (n = t.length) <= (i = index) || i < 0) - return next = null; - if ((e = tabAt(t, index)) != null && e.hash < 0) { - if (e instanceof ForwardingNode) { - tab = ((ForwardingNode)e).nextTable; - e = null; - continue; - } - else if (e instanceof TreeBin) - e = ((TreeBin)e).first; - else - e = null; - } - if ((index += baseSize) >= n) - index = ++baseIndex; // visit upper slots if present - } - } - } - - /* - * Task classes. Coded in a regular but ugly format/style to - * simplify checks that each variant differs in the right way from - * others. The null screenings exist because compilers cannot tell - * that we've already null-checked task arguments, so we force - * simplest hoisted bypass to help avoid convoluted traps. - */ - @SuppressWarnings("serial") - static final class ForEachKeyTask - extends BulkTask { - final Action action; - ForEachKeyTask - (BulkTask p, int b, int i, int f, Node[] t, - Action action) { - super(p, b, i, f, t); - this.action = action; - } - public final void compute() { - final Action action; - if ((action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - new ForEachKeyTask - (this, batch >>>= 1, baseLimit = h, f, tab, - action).fork(); - } - for (Node p; (p = advance()) != null;) - action.apply(p.key); - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachValueTask - extends BulkTask { - final Action action; - ForEachValueTask - (BulkTask p, int b, int i, int f, Node[] t, - Action action) { - super(p, b, i, f, t); - this.action = action; - } - public final void compute() { - final Action action; - if ((action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - new ForEachValueTask - (this, batch >>>= 1, baseLimit = h, f, tab, - action).fork(); - } - for (Node p; (p = advance()) != null;) - action.apply(p.val); - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachEntryTask - extends BulkTask { - final Action> action; - ForEachEntryTask - (BulkTask p, int b, int i, int f, Node[] t, - Action> action) { - super(p, b, i, f, t); - this.action = action; - } - public final void compute() { - final Action> action; - if ((action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - new ForEachEntryTask - (this, batch >>>= 1, baseLimit = h, f, tab, - action).fork(); - } - for (Node p; (p = advance()) != null; ) - action.apply(p); - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachMappingTask - extends BulkTask { - final BiAction action; - ForEachMappingTask - (BulkTask p, int b, int i, int f, Node[] t, - BiAction action) { - super(p, b, i, f, t); - this.action = action; - } - public final void compute() { - final BiAction action; - if ((action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - new ForEachMappingTask - (this, batch >>>= 1, baseLimit = h, f, tab, - action).fork(); - } - for (Node p; (p = advance()) != null; ) - action.apply(p.key, p.val); - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachTransformedKeyTask - extends BulkTask { - final Fun transformer; - final Action action; - ForEachTransformedKeyTask - (BulkTask p, int b, int i, int f, Node[] t, - Fun transformer, Action action) { - super(p, b, i, f, t); - this.transformer = transformer; this.action = action; - } - public final void compute() { - final Fun transformer; - final Action action; - if ((transformer = this.transformer) != null && - (action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - new ForEachTransformedKeyTask - (this, batch >>>= 1, baseLimit = h, f, tab, - transformer, action).fork(); - } - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p.key)) != null) - action.apply(u); - } - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachTransformedValueTask - extends BulkTask { - final Fun transformer; - final Action action; - ForEachTransformedValueTask - (BulkTask p, int b, int i, int f, Node[] t, - Fun transformer, Action action) { - super(p, b, i, f, t); - this.transformer = transformer; this.action = action; - } - public final void compute() { - final Fun transformer; - final Action action; - if ((transformer = this.transformer) != null && - (action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - new ForEachTransformedValueTask - (this, batch >>>= 1, baseLimit = h, f, tab, - transformer, action).fork(); - } - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p.val)) != null) - action.apply(u); - } - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachTransformedEntryTask - extends BulkTask { - final Fun, ? extends U> transformer; - final Action action; - ForEachTransformedEntryTask - (BulkTask p, int b, int i, int f, Node[] t, - Fun, ? extends U> transformer, Action action) { - super(p, b, i, f, t); - this.transformer = transformer; this.action = action; - } - public final void compute() { - final Fun, ? extends U> transformer; - final Action action; - if ((transformer = this.transformer) != null && - (action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - new ForEachTransformedEntryTask - (this, batch >>>= 1, baseLimit = h, f, tab, - transformer, action).fork(); - } - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p)) != null) - action.apply(u); - } - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachTransformedMappingTask - extends BulkTask { - final BiFun transformer; - final Action action; - ForEachTransformedMappingTask - (BulkTask p, int b, int i, int f, Node[] t, - BiFun transformer, - Action action) { - super(p, b, i, f, t); - this.transformer = transformer; this.action = action; - } - public final void compute() { - final BiFun transformer; - final Action action; - if ((transformer = this.transformer) != null && - (action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - new ForEachTransformedMappingTask - (this, batch >>>= 1, baseLimit = h, f, tab, - transformer, action).fork(); - } - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p.key, p.val)) != null) - action.apply(u); - } - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class SearchKeysTask - extends BulkTask { - final Fun searchFunction; - final AtomicReference result; - SearchKeysTask - (BulkTask p, int b, int i, int f, Node[] t, - Fun searchFunction, - AtomicReference result) { - super(p, b, i, f, t); - this.searchFunction = searchFunction; this.result = result; - } - public final U getRawResult() { return result.get(); } - public final void compute() { - final Fun searchFunction; - final AtomicReference result; - if ((searchFunction = this.searchFunction) != null && - (result = this.result) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - if (result.get() != null) - return; - addToPendingCount(1); - new SearchKeysTask - (this, batch >>>= 1, baseLimit = h, f, tab, - searchFunction, result).fork(); - } - while (result.get() == null) { - U u; - Node p; - if ((p = advance()) == null) { - propagateCompletion(); - break; - } - if ((u = searchFunction.apply(p.key)) != null) { - if (result.compareAndSet(null, u)) - quietlyCompleteRoot(); - break; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class SearchValuesTask - extends BulkTask { - final Fun searchFunction; - final AtomicReference result; - SearchValuesTask - (BulkTask p, int b, int i, int f, Node[] t, - Fun searchFunction, - AtomicReference result) { - super(p, b, i, f, t); - this.searchFunction = searchFunction; this.result = result; - } - public final U getRawResult() { return result.get(); } - public final void compute() { - final Fun searchFunction; - final AtomicReference result; - if ((searchFunction = this.searchFunction) != null && - (result = this.result) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - if (result.get() != null) - return; - addToPendingCount(1); - new SearchValuesTask - (this, batch >>>= 1, baseLimit = h, f, tab, - searchFunction, result).fork(); - } - while (result.get() == null) { - U u; - Node p; - if ((p = advance()) == null) { - propagateCompletion(); - break; - } - if ((u = searchFunction.apply(p.val)) != null) { - if (result.compareAndSet(null, u)) - quietlyCompleteRoot(); - break; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class SearchEntriesTask - extends BulkTask { - final Fun, ? extends U> searchFunction; - final AtomicReference result; - SearchEntriesTask - (BulkTask p, int b, int i, int f, Node[] t, - Fun, ? extends U> searchFunction, - AtomicReference result) { - super(p, b, i, f, t); - this.searchFunction = searchFunction; this.result = result; - } - public final U getRawResult() { return result.get(); } - public final void compute() { - final Fun, ? extends U> searchFunction; - final AtomicReference result; - if ((searchFunction = this.searchFunction) != null && - (result = this.result) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - if (result.get() != null) - return; - addToPendingCount(1); - new SearchEntriesTask - (this, batch >>>= 1, baseLimit = h, f, tab, - searchFunction, result).fork(); - } - while (result.get() == null) { - U u; - Node p; - if ((p = advance()) == null) { - propagateCompletion(); - break; - } - if ((u = searchFunction.apply(p)) != null) { - if (result.compareAndSet(null, u)) - quietlyCompleteRoot(); - return; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class SearchMappingsTask - extends BulkTask { - final BiFun searchFunction; - final AtomicReference result; - SearchMappingsTask - (BulkTask p, int b, int i, int f, Node[] t, - BiFun searchFunction, - AtomicReference result) { - super(p, b, i, f, t); - this.searchFunction = searchFunction; this.result = result; - } - public final U getRawResult() { return result.get(); } - public final void compute() { - final BiFun searchFunction; - final AtomicReference result; - if ((searchFunction = this.searchFunction) != null && - (result = this.result) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - if (result.get() != null) - return; - addToPendingCount(1); - new SearchMappingsTask - (this, batch >>>= 1, baseLimit = h, f, tab, - searchFunction, result).fork(); - } - while (result.get() == null) { - U u; - Node p; - if ((p = advance()) == null) { - propagateCompletion(); - break; - } - if ((u = searchFunction.apply(p.key, p.val)) != null) { - if (result.compareAndSet(null, u)) - quietlyCompleteRoot(); - break; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class ReduceKeysTask - extends BulkTask { - final BiFun reducer; - K result; - ReduceKeysTask rights, nextRight; - ReduceKeysTask - (BulkTask p, int b, int i, int f, Node[] t, - ReduceKeysTask nextRight, - BiFun reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.reducer = reducer; - } - public final K getRawResult() { return result; } - public final void compute() { - final BiFun reducer; - if ((reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new ReduceKeysTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, reducer)).fork(); - } - K r = null; - for (Node p; (p = advance()) != null; ) { - K u = p.key; - r = (r == null) ? u : u == null ? r : reducer.apply(r, u); - } - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") ReduceKeysTask - t = (ReduceKeysTask)c, - s = t.rights; - while (s != null) { - K tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class ReduceValuesTask - extends BulkTask { - final BiFun reducer; - V result; - ReduceValuesTask rights, nextRight; - ReduceValuesTask - (BulkTask p, int b, int i, int f, Node[] t, - ReduceValuesTask nextRight, - BiFun reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.reducer = reducer; - } - public final V getRawResult() { return result; } - public final void compute() { - final BiFun reducer; - if ((reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new ReduceValuesTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, reducer)).fork(); - } - V r = null; - for (Node p; (p = advance()) != null; ) { - V v = p.val; - r = (r == null) ? v : reducer.apply(r, v); - } - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") ReduceValuesTask - t = (ReduceValuesTask)c, - s = t.rights; - while (s != null) { - V tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class ReduceEntriesTask - extends BulkTask> { - final BiFun, Map.Entry, ? extends Map.Entry> reducer; - Map.Entry result; - ReduceEntriesTask rights, nextRight; - ReduceEntriesTask - (BulkTask p, int b, int i, int f, Node[] t, - ReduceEntriesTask nextRight, - BiFun, Map.Entry, ? extends Map.Entry> reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.reducer = reducer; - } - public final Map.Entry getRawResult() { return result; } - public final void compute() { - final BiFun, Map.Entry, ? extends Map.Entry> reducer; - if ((reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new ReduceEntriesTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, reducer)).fork(); - } - Map.Entry r = null; - for (Node p; (p = advance()) != null; ) - r = (r == null) ? p : reducer.apply(r, p); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") ReduceEntriesTask - t = (ReduceEntriesTask)c, - s = t.rights; - while (s != null) { - Map.Entry tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceKeysTask - extends BulkTask { - final Fun transformer; - final BiFun reducer; - U result; - MapReduceKeysTask rights, nextRight; - MapReduceKeysTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceKeysTask nextRight, - Fun transformer, - BiFun reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.reducer = reducer; - } - public final U getRawResult() { return result; } - public final void compute() { - final Fun transformer; - final BiFun reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceKeysTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, reducer)).fork(); - } - U r = null; - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p.key)) != null) - r = (r == null) ? u : reducer.apply(r, u); - } - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceKeysTask - t = (MapReduceKeysTask)c, - s = t.rights; - while (s != null) { - U tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceValuesTask - extends BulkTask { - final Fun transformer; - final BiFun reducer; - U result; - MapReduceValuesTask rights, nextRight; - MapReduceValuesTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceValuesTask nextRight, - Fun transformer, - BiFun reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.reducer = reducer; - } - public final U getRawResult() { return result; } - public final void compute() { - final Fun transformer; - final BiFun reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceValuesTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, reducer)).fork(); - } - U r = null; - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p.val)) != null) - r = (r == null) ? u : reducer.apply(r, u); - } - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceValuesTask - t = (MapReduceValuesTask)c, - s = t.rights; - while (s != null) { - U tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceEntriesTask - extends BulkTask { - final Fun, ? extends U> transformer; - final BiFun reducer; - U result; - MapReduceEntriesTask rights, nextRight; - MapReduceEntriesTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceEntriesTask nextRight, - Fun, ? extends U> transformer, - BiFun reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.reducer = reducer; - } - public final U getRawResult() { return result; } - public final void compute() { - final Fun, ? extends U> transformer; - final BiFun reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceEntriesTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, reducer)).fork(); - } - U r = null; - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p)) != null) - r = (r == null) ? u : reducer.apply(r, u); - } - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceEntriesTask - t = (MapReduceEntriesTask)c, - s = t.rights; - while (s != null) { - U tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceMappingsTask - extends BulkTask { - final BiFun transformer; - final BiFun reducer; - U result; - MapReduceMappingsTask rights, nextRight; - MapReduceMappingsTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceMappingsTask nextRight, - BiFun transformer, - BiFun reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.reducer = reducer; - } - public final U getRawResult() { return result; } - public final void compute() { - final BiFun transformer; - final BiFun reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceMappingsTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, reducer)).fork(); - } - U r = null; - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p.key, p.val)) != null) - r = (r == null) ? u : reducer.apply(r, u); - } - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceMappingsTask - t = (MapReduceMappingsTask)c, - s = t.rights; - while (s != null) { - U tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceKeysToDoubleTask - extends BulkTask { - final ObjectToDouble transformer; - final DoubleByDoubleToDouble reducer; - final double basis; - double result; - MapReduceKeysToDoubleTask rights, nextRight; - MapReduceKeysToDoubleTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceKeysToDoubleTask nextRight, - ObjectToDouble transformer, - double basis, - DoubleByDoubleToDouble reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; this.reducer = reducer; - } - public final Double getRawResult() { return result; } - public final void compute() { - final ObjectToDouble transformer; - final DoubleByDoubleToDouble reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - double r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceKeysToDoubleTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.apply(r, transformer.apply(p.key)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceKeysToDoubleTask - t = (MapReduceKeysToDoubleTask)c, - s = t.rights; - while (s != null) { - t.result = reducer.apply(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceValuesToDoubleTask - extends BulkTask { - final ObjectToDouble transformer; - final DoubleByDoubleToDouble reducer; - final double basis; - double result; - MapReduceValuesToDoubleTask rights, nextRight; - MapReduceValuesToDoubleTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceValuesToDoubleTask nextRight, - ObjectToDouble transformer, - double basis, - DoubleByDoubleToDouble reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; this.reducer = reducer; - } - public final Double getRawResult() { return result; } - public final void compute() { - final ObjectToDouble transformer; - final DoubleByDoubleToDouble reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - double r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceValuesToDoubleTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.apply(r, transformer.apply(p.val)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceValuesToDoubleTask - t = (MapReduceValuesToDoubleTask)c, - s = t.rights; - while (s != null) { - t.result = reducer.apply(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceEntriesToDoubleTask - extends BulkTask { - final ObjectToDouble> transformer; - final DoubleByDoubleToDouble reducer; - final double basis; - double result; - MapReduceEntriesToDoubleTask rights, nextRight; - MapReduceEntriesToDoubleTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceEntriesToDoubleTask nextRight, - ObjectToDouble> transformer, - double basis, - DoubleByDoubleToDouble reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; this.reducer = reducer; - } - public final Double getRawResult() { return result; } - public final void compute() { - final ObjectToDouble> transformer; - final DoubleByDoubleToDouble reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - double r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceEntriesToDoubleTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.apply(r, transformer.apply(p)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceEntriesToDoubleTask - t = (MapReduceEntriesToDoubleTask)c, - s = t.rights; - while (s != null) { - t.result = reducer.apply(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceMappingsToDoubleTask - extends BulkTask { - final ObjectByObjectToDouble transformer; - final DoubleByDoubleToDouble reducer; - final double basis; - double result; - MapReduceMappingsToDoubleTask rights, nextRight; - MapReduceMappingsToDoubleTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceMappingsToDoubleTask nextRight, - ObjectByObjectToDouble transformer, - double basis, - DoubleByDoubleToDouble reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; this.reducer = reducer; - } - public final Double getRawResult() { return result; } - public final void compute() { - final ObjectByObjectToDouble transformer; - final DoubleByDoubleToDouble reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - double r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceMappingsToDoubleTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.apply(r, transformer.apply(p.key, p.val)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceMappingsToDoubleTask - t = (MapReduceMappingsToDoubleTask)c, - s = t.rights; - while (s != null) { - t.result = reducer.apply(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceKeysToLongTask - extends BulkTask { - final ObjectToLong transformer; - final LongByLongToLong reducer; - final long basis; - long result; - MapReduceKeysToLongTask rights, nextRight; - MapReduceKeysToLongTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceKeysToLongTask nextRight, - ObjectToLong transformer, - long basis, - LongByLongToLong reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; this.reducer = reducer; - } - public final Long getRawResult() { return result; } - public final void compute() { - final ObjectToLong transformer; - final LongByLongToLong reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - long r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceKeysToLongTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.apply(r, transformer.apply(p.key)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceKeysToLongTask - t = (MapReduceKeysToLongTask)c, - s = t.rights; - while (s != null) { - t.result = reducer.apply(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceValuesToLongTask - extends BulkTask { - final ObjectToLong transformer; - final LongByLongToLong reducer; - final long basis; - long result; - MapReduceValuesToLongTask rights, nextRight; - MapReduceValuesToLongTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceValuesToLongTask nextRight, - ObjectToLong transformer, - long basis, - LongByLongToLong reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; this.reducer = reducer; - } - public final Long getRawResult() { return result; } - public final void compute() { - final ObjectToLong transformer; - final LongByLongToLong reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - long r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceValuesToLongTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.apply(r, transformer.apply(p.val)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceValuesToLongTask - t = (MapReduceValuesToLongTask)c, - s = t.rights; - while (s != null) { - t.result = reducer.apply(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceEntriesToLongTask - extends BulkTask { - final ObjectToLong> transformer; - final LongByLongToLong reducer; - final long basis; - long result; - MapReduceEntriesToLongTask rights, nextRight; - MapReduceEntriesToLongTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceEntriesToLongTask nextRight, - ObjectToLong> transformer, - long basis, - LongByLongToLong reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; this.reducer = reducer; - } - public final Long getRawResult() { return result; } - public final void compute() { - final ObjectToLong> transformer; - final LongByLongToLong reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - long r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceEntriesToLongTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.apply(r, transformer.apply(p)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceEntriesToLongTask - t = (MapReduceEntriesToLongTask)c, - s = t.rights; - while (s != null) { - t.result = reducer.apply(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceMappingsToLongTask - extends BulkTask { - final ObjectByObjectToLong transformer; - final LongByLongToLong reducer; - final long basis; - long result; - MapReduceMappingsToLongTask rights, nextRight; - MapReduceMappingsToLongTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceMappingsToLongTask nextRight, - ObjectByObjectToLong transformer, - long basis, - LongByLongToLong reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; this.reducer = reducer; - } - public final Long getRawResult() { return result; } - public final void compute() { - final ObjectByObjectToLong transformer; - final LongByLongToLong reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - long r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceMappingsToLongTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.apply(r, transformer.apply(p.key, p.val)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceMappingsToLongTask - t = (MapReduceMappingsToLongTask)c, - s = t.rights; - while (s != null) { - t.result = reducer.apply(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceKeysToIntTask - extends BulkTask { - final ObjectToInt transformer; - final IntByIntToInt reducer; - final int basis; - int result; - MapReduceKeysToIntTask rights, nextRight; - MapReduceKeysToIntTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceKeysToIntTask nextRight, - ObjectToInt transformer, - int basis, - IntByIntToInt reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; this.reducer = reducer; - } - public final Integer getRawResult() { return result; } - public final void compute() { - final ObjectToInt transformer; - final IntByIntToInt reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - int r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceKeysToIntTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.apply(r, transformer.apply(p.key)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceKeysToIntTask - t = (MapReduceKeysToIntTask)c, - s = t.rights; - while (s != null) { - t.result = reducer.apply(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceValuesToIntTask - extends BulkTask { - final ObjectToInt transformer; - final IntByIntToInt reducer; - final int basis; - int result; - MapReduceValuesToIntTask rights, nextRight; - MapReduceValuesToIntTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceValuesToIntTask nextRight, - ObjectToInt transformer, - int basis, - IntByIntToInt reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; this.reducer = reducer; - } - public final Integer getRawResult() { return result; } - public final void compute() { - final ObjectToInt transformer; - final IntByIntToInt reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - int r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceValuesToIntTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.apply(r, transformer.apply(p.val)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceValuesToIntTask - t = (MapReduceValuesToIntTask)c, - s = t.rights; - while (s != null) { - t.result = reducer.apply(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceEntriesToIntTask - extends BulkTask { - final ObjectToInt> transformer; - final IntByIntToInt reducer; - final int basis; - int result; - MapReduceEntriesToIntTask rights, nextRight; - MapReduceEntriesToIntTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceEntriesToIntTask nextRight, - ObjectToInt> transformer, - int basis, - IntByIntToInt reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; this.reducer = reducer; - } - public final Integer getRawResult() { return result; } - public final void compute() { - final ObjectToInt> transformer; - final IntByIntToInt reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - int r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceEntriesToIntTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.apply(r, transformer.apply(p)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceEntriesToIntTask - t = (MapReduceEntriesToIntTask)c, - s = t.rights; - while (s != null) { - t.result = reducer.apply(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceMappingsToIntTask - extends BulkTask { - final ObjectByObjectToInt transformer; - final IntByIntToInt reducer; - final int basis; - int result; - MapReduceMappingsToIntTask rights, nextRight; - MapReduceMappingsToIntTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceMappingsToIntTask nextRight, - ObjectByObjectToInt transformer, - int basis, - IntByIntToInt reducer) { - super(p, b, i, f, t); this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; this.reducer = reducer; - } - public final Integer getRawResult() { return result; } - public final void compute() { - final ObjectByObjectToInt transformer; - final IntByIntToInt reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - int r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i;) { - addToPendingCount(1); - (rights = new MapReduceMappingsToIntTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.apply(r, transformer.apply(p.key, p.val)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") MapReduceMappingsToIntTask - t = (MapReduceMappingsToIntTask)c, - s = t.rights; - while (s != null) { - t.result = reducer.apply(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - /* ---------------- Counters -------------- */ - - // Adapted from LongAdder and Striped64. - // See their internal docs for explanation. - - // A padded cell for distributing counts - static final class CounterCell { - volatile long p0, p1, p2, p3, p4, p5, p6; - volatile long value; - volatile long q0, q1, q2, q3, q4, q5, q6; - CounterCell(long x) { value = x; } - } - - /** - * Holder for the thread-local hash code determining which - * CounterCell to use. The code is initialized via the - * counterHashCodeGenerator, but may be moved upon collisions. - */ - static final class CounterHashCode { - int code; - } - - /** - * Generates initial value for per-thread CounterHashCodes. - */ - static final AtomicInteger counterHashCodeGenerator = new AtomicInteger(); - - /** - * Increment for counterHashCodeGenerator. See class ThreadLocal - * for explanation. - */ - static final int SEED_INCREMENT = 0x61c88647; - - /** - * Per-thread counter hash codes. Shared across all instances. - */ - static final ThreadLocal threadCounterHashCode = - new ThreadLocal(); - - - final long sumCount() { - CounterCell[] as = counterCells; CounterCell a; - long sum = baseCount; - if (as != null) { - for (int i = 0; i < as.length; ++i) { - if ((a = as[i]) != null) - sum += a.value; - } - } - return sum; - } - - // See LongAdder version for explanation - private final void fullAddCount(long x, CounterHashCode hc, - boolean wasUncontended) { - int h; - if (hc == null) { - hc = new CounterHashCode(); - int s = counterHashCodeGenerator.addAndGet(SEED_INCREMENT); - h = hc.code = (s == 0) ? 1 : s; // Avoid zero - threadCounterHashCode.set(hc); - } - else - h = hc.code; - boolean collide = false; // True if last slot nonempty - for (;;) { - CounterCell[] as; CounterCell a; int n; long v; - if ((as = counterCells) != null && (n = as.length) > 0) { - if ((a = as[(n - 1) & h]) == null) { - if (cellsBusy == 0) { // Try to attach new Cell - CounterCell r = new CounterCell(x); // Optimistic create - if (cellsBusy == 0 && - U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { - boolean created = false; - try { // Recheck under lock - CounterCell[] rs; int m, j; - if ((rs = counterCells) != null && - (m = rs.length) > 0 && - rs[j = (m - 1) & h] == null) { - rs[j] = r; - created = true; - } - } finally { - cellsBusy = 0; - } - if (created) - break; - continue; // Slot is now non-empty - } - } - collide = false; - } - else if (!wasUncontended) // CAS already known to fail - wasUncontended = true; // Continue after rehash - else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) - break; - else if (counterCells != as || n >= NCPU) - collide = false; // At max size or stale - else if (!collide) - collide = true; - else if (cellsBusy == 0 && - U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { - try { - if (counterCells == as) {// Expand table unless stale - CounterCell[] rs = new CounterCell[n << 1]; - for (int i = 0; i < n; ++i) - rs[i] = as[i]; - counterCells = rs; - } - } finally { - cellsBusy = 0; - } - collide = false; - continue; // Retry with expanded table - } - h ^= h << 13; // Rehash - h ^= h >>> 17; - h ^= h << 5; - } - else if (cellsBusy == 0 && counterCells == as && - U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { - boolean init = false; - try { // Initialize table - if (counterCells == as) { - CounterCell[] rs = new CounterCell[2]; - rs[h & 1] = new CounterCell(x); - counterCells = rs; - init = true; - } - } finally { - cellsBusy = 0; - } - if (init) - break; - } - else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x)) - break; // Fall back on using base - } - hc.code = h; // Record index for next time - } - - // Unsafe mechanics - private static final sun.misc.Unsafe U; - private static final long SIZECTL; - private static final long TRANSFERINDEX; - private static final long TRANSFERORIGIN; - private static final long BASECOUNT; - private static final long CELLSBUSY; - private static final long CELLVALUE; - private static final long ABASE; - private static final int ASHIFT; - - static { - try { - U = getUnsafe(); - Class k = ConcurrentHashMapV8.class; - SIZECTL = U.objectFieldOffset - (k.getDeclaredField("sizeCtl")); - TRANSFERINDEX = U.objectFieldOffset - (k.getDeclaredField("transferIndex")); - TRANSFERORIGIN = U.objectFieldOffset - (k.getDeclaredField("transferOrigin")); - BASECOUNT = U.objectFieldOffset - (k.getDeclaredField("baseCount")); - CELLSBUSY = U.objectFieldOffset - (k.getDeclaredField("cellsBusy")); - Class ck = CounterCell.class; - CELLVALUE = U.objectFieldOffset - (ck.getDeclaredField("value")); - Class ak = Node[].class; - ABASE = U.arrayBaseOffset(ak); - int scale = U.arrayIndexScale(ak); - if ((scale & (scale - 1)) != 0) - throw new Error("data type scale not a power of two"); - ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); - } catch (Exception e) { - throw new Error(e); - } - } - - /** - * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. - * Replace with a simple call to Unsafe.getUnsafe when integrating - * into a jdk. - * - * @return a sun.misc.Unsafe - */ - private static sun.misc.Unsafe getUnsafe() { - try { - return sun.misc.Unsafe.getUnsafe(); - } catch (SecurityException tryReflectionInstead) {} - try { - return java.security.AccessController.doPrivileged - (new java.security.PrivilegedExceptionAction() { - public sun.misc.Unsafe run() throws Exception { - Class k = sun.misc.Unsafe.class; - for (java.lang.reflect.Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) - return k.cast(x); - } - throw new NoSuchFieldError("the Unsafe"); - }}); - } catch (java.security.PrivilegedActionException e) { - throw new RuntimeException("Could not initialize intrinsics", - e.getCause()); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/internal/chmv8/CountedCompleter.java b/api/src/main/java/org/asynchttpclient/internal/chmv8/CountedCompleter.java deleted file mode 100644 index 50eaf4af3f..0000000000 --- a/api/src/main/java/org/asynchttpclient/internal/chmv8/CountedCompleter.java +++ /dev/null @@ -1,769 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project 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. - */ - -/* - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -package org.asynchttpclient.internal.chmv8; - -import java.util.concurrent.RecursiveAction; - -/** - * A {@link ForkJoinTask} with a completion action performed when - * triggered and there are no remaining pending actions. - * CountedCompleters are in general more robust in the - * presence of subtask stalls and blockage than are other forms of - * ForkJoinTasks, but are less intuitive to program. Uses of - * CountedCompleter are similar to those of other completion based - * components (such as {@link java.nio.channels.CompletionHandler}) - * except that multiple pending completions may be necessary - * to trigger the completion action {@link #onCompletion(CountedCompleter)}, - * not just one. - * Unless initialized otherwise, the {@linkplain #getPendingCount pending - * count} starts at zero, but may be (atomically) changed using - * methods {@link #setPendingCount}, {@link #addToPendingCount}, and - * {@link #compareAndSetPendingCount}. Upon invocation of {@link - * #tryComplete}, if the pending action count is nonzero, it is - * decremented; otherwise, the completion action is performed, and if - * this completer itself has a completer, the process is continued - * with its completer. As is the case with related synchronization - * components such as {@link java.util.concurrent.Phaser Phaser} and - * {@link java.util.concurrent.Semaphore Semaphore}, these methods - * affect only internal counts; they do not establish any further - * internal bookkeeping. In particular, the identities of pending - * tasks are not maintained. As illustrated below, you can create - * subclasses that do record some or all pending tasks or their - * results when needed. As illustrated below, utility methods - * supporting customization of completion traversals are also - * provided. However, because CountedCompleters provide only basic - * synchronization mechanisms, it may be useful to create further - * abstract subclasses that maintain linkages, fields, and additional - * support methods appropriate for a set of related usages. - * - *

A concrete CountedCompleter class must define method {@link - * #compute}, that should in most cases (as illustrated below), invoke - * {@code tryComplete()} once before returning. The class may also - * optionally override method {@link #onCompletion(CountedCompleter)} - * to perform an action upon normal completion, and method - * {@link #onExceptionalCompletion(Throwable, CountedCompleter)} to - * perform an action upon any exception. - * - *

CountedCompleters most often do not bear results, in which case - * they are normally declared as {@code CountedCompleter}, and - * will always return {@code null} as a result value. In other cases, - * you should override method {@link #getRawResult} to provide a - * result from {@code join(), invoke()}, and related methods. In - * general, this method should return the value of a field (or a - * function of one or more fields) of the CountedCompleter object that - * holds the result upon completion. Method {@link #setRawResult} by - * default plays no role in CountedCompleters. It is possible, but - * rarely applicable, to override this method to maintain other - * objects or fields holding result data. - * - *

A CountedCompleter that does not itself have a completer (i.e., - * one for which {@link #getCompleter} returns {@code null}) can be - * used as a regular ForkJoinTask with this added functionality. - * However, any completer that in turn has another completer serves - * only as an internal helper for other computations, so its own task - * status (as reported in methods such as {@link ForkJoinTask#isDone}) - * is arbitrary; this status changes only upon explicit invocations of - * {@link #complete}, {@link ForkJoinTask#cancel}, - * {@link ForkJoinTask#completeExceptionally(Throwable)} or upon - * exceptional completion of method {@code compute}. Upon any - * exceptional completion, the exception may be relayed to a task's - * completer (and its completer, and so on), if one exists and it has - * not otherwise already completed. Similarly, cancelling an internal - * CountedCompleter has only a local effect on that completer, so is - * not often useful. - * - *

Sample Usages. - * - *

Parallel recursive decomposition. CountedCompleters may - * be arranged in trees similar to those often used with {@link - * RecursiveAction}s, although the constructions involved in setting - * them up typically vary. Here, the completer of each task is its - * parent in the computation tree. Even though they entail a bit more - * bookkeeping, CountedCompleters may be better choices when applying - * a possibly time-consuming operation (that cannot be further - * subdivided) to each element of an array or collection; especially - * when the operation takes a significantly different amount of time - * to complete for some elements than others, either because of - * intrinsic variation (for example I/O) or auxiliary effects such as - * garbage collection. Because CountedCompleters provide their own - * continuations, other threads need not block waiting to perform - * them. - * - *

For example, here is an initial version of a class that uses - * divide-by-two recursive decomposition to divide work into single - * pieces (leaf tasks). Even when work is split into individual calls, - * tree-based techniques are usually preferable to directly forking - * leaf tasks, because they reduce inter-thread communication and - * improve load balancing. In the recursive case, the second of each - * pair of subtasks to finish triggers completion of its parent - * (because no result combination is performed, the default no-op - * implementation of method {@code onCompletion} is not overridden). - * A static utility method sets up the base task and invokes it - * (here, implicitly using the {@link ForkJoinPool#commonPool()}). - * - *

 {@code
- * class MyOperation { void apply(E e) { ... }  }
- *
- * class ForEach extends CountedCompleter {
- *
- *   public static  void forEach(E[] array, MyOperation op) {
- *     new ForEach(null, array, op, 0, array.length).invoke();
- *   }
- *
- *   final E[] array; final MyOperation op; final int lo, hi;
- *   ForEach(CountedCompleter p, E[] array, MyOperation op, int lo, int hi) {
- *     super(p);
- *     this.array = array; this.op = op; this.lo = lo; this.hi = hi;
- *   }
- *
- *   public void compute() { // version 1
- *     if (hi - lo >= 2) {
- *       int mid = (lo + hi) >>> 1;
- *       setPendingCount(2); // must set pending count before fork
- *       new ForEach(this, array, op, mid, hi).fork(); // right child
- *       new ForEach(this, array, op, lo, mid).fork(); // left child
- *     }
- *     else if (hi > lo)
- *       op.apply(array[lo]);
- *     tryComplete();
- *   }
- * }}
- * - * This design can be improved by noticing that in the recursive case, - * the task has nothing to do after forking its right task, so can - * directly invoke its left task before returning. (This is an analog - * of tail recursion removal.) Also, because the task returns upon - * executing its left task (rather than falling through to invoke - * {@code tryComplete}) the pending count is set to one: - * - *
 {@code
- * class ForEach ...
- *   public void compute() { // version 2
- *     if (hi - lo >= 2) {
- *       int mid = (lo + hi) >>> 1;
- *       setPendingCount(1); // only one pending
- *       new ForEach(this, array, op, mid, hi).fork(); // right child
- *       new ForEach(this, array, op, lo, mid).compute(); // direct invoke
- *     }
- *     else {
- *       if (hi > lo)
- *         op.apply(array[lo]);
- *       tryComplete();
- *     }
- *   }
- * }
- * - * As a further improvement, notice that the left task need not even exist. - * Instead of creating a new one, we can iterate using the original task, - * and add a pending count for each fork. Additionally, because no task - * in this tree implements an {@link #onCompletion(CountedCompleter)} method, - * {@code tryComplete()} can be replaced with {@link #propagateCompletion}. - * - *
 {@code
- * class ForEach ...
- *   public void compute() { // version 3
- *     int l = lo,  h = hi;
- *     while (h - l >= 2) {
- *       int mid = (l + h) >>> 1;
- *       addToPendingCount(1);
- *       new ForEach(this, array, op, mid, h).fork(); // right child
- *       h = mid;
- *     }
- *     if (h > l)
- *       op.apply(array[l]);
- *     propagateCompletion();
- *   }
- * }
- * - * Additional improvements of such classes might entail precomputing - * pending counts so that they can be established in constructors, - * specializing classes for leaf steps, subdividing by say, four, - * instead of two per iteration, and using an adaptive threshold - * instead of always subdividing down to single elements. - * - *

Searching. A tree of CountedCompleters can search for a - * value or property in different parts of a data structure, and - * report a result in an {@link - * java.util.concurrent.atomic.AtomicReference AtomicReference} as - * soon as one is found. The others can poll the result to avoid - * unnecessary work. (You could additionally {@linkplain #cancel - * cancel} other tasks, but it is usually simpler and more efficient - * to just let them notice that the result is set and if so skip - * further processing.) Illustrating again with an array using full - * partitioning (again, in practice, leaf tasks will almost always - * process more than one element): - * - *

 {@code
- * class Searcher extends CountedCompleter {
- *   final E[] array; final AtomicReference result; final int lo, hi;
- *   Searcher(CountedCompleter p, E[] array, AtomicReference result, int lo, int hi) {
- *     super(p);
- *     this.array = array; this.result = result; this.lo = lo; this.hi = hi;
- *   }
- *   public E getRawResult() { return result.get(); }
- *   public void compute() { // similar to ForEach version 3
- *     int l = lo,  h = hi;
- *     while (result.get() == null && h >= l) {
- *       if (h - l >= 2) {
- *         int mid = (l + h) >>> 1;
- *         addToPendingCount(1);
- *         new Searcher(this, array, result, mid, h).fork();
- *         h = mid;
- *       }
- *       else {
- *         E x = array[l];
- *         if (matches(x) && result.compareAndSet(null, x))
- *           quietlyCompleteRoot(); // root task is now joinable
- *         break;
- *       }
- *     }
- *     tryComplete(); // normally complete whether or not found
- *   }
- *   boolean matches(E e) { ... } // return true if found
- *
- *   public static  E search(E[] array) {
- *       return new Searcher(null, array, new AtomicReference(), 0, array.length).invoke();
- *   }
- * }}
- * - * In this example, as well as others in which tasks have no other - * effects except to compareAndSet a common result, the trailing - * unconditional invocation of {@code tryComplete} could be made - * conditional ({@code if (result.get() == null) tryComplete();}) - * because no further bookkeeping is required to manage completions - * once the root task completes. - * - *

Recording subtasks. CountedCompleter tasks that combine - * results of multiple subtasks usually need to access these results - * in method {@link #onCompletion(CountedCompleter)}. As illustrated in the following - * class (that performs a simplified form of map-reduce where mappings - * and reductions are all of type {@code E}), one way to do this in - * divide and conquer designs is to have each subtask record its - * sibling, so that it can be accessed in method {@code onCompletion}. - * This technique applies to reductions in which the order of - * combining left and right results does not matter; ordered - * reductions require explicit left/right designations. Variants of - * other streamlinings seen in the above examples may also apply. - * - *

 {@code
- * class MyMapper { E apply(E v) {  ...  } }
- * class MyReducer { E apply(E x, E y) {  ...  } }
- * class MapReducer extends CountedCompleter {
- *   final E[] array; final MyMapper mapper;
- *   final MyReducer reducer; final int lo, hi;
- *   MapReducer sibling;
- *   E result;
- *   MapReducer(CountedCompleter p, E[] array, MyMapper mapper,
- *              MyReducer reducer, int lo, int hi) {
- *     super(p);
- *     this.array = array; this.mapper = mapper;
- *     this.reducer = reducer; this.lo = lo; this.hi = hi;
- *   }
- *   public void compute() {
- *     if (hi - lo >= 2) {
- *       int mid = (lo + hi) >>> 1;
- *       MapReducer left = new MapReducer(this, array, mapper, reducer, lo, mid);
- *       MapReducer right = new MapReducer(this, array, mapper, reducer, mid, hi);
- *       left.sibling = right;
- *       right.sibling = left;
- *       setPendingCount(1); // only right is pending
- *       right.fork();
- *       left.compute();     // directly execute left
- *     }
- *     else {
- *       if (hi > lo)
- *           result = mapper.apply(array[lo]);
- *       tryComplete();
- *     }
- *   }
- *   public void onCompletion(CountedCompleter caller) {
- *     if (caller != this) {
- *       MapReducer child = (MapReducer)caller;
- *       MapReducer sib = child.sibling;
- *       if (sib == null || sib.result == null)
- *         result = child.result;
- *       else
- *         result = reducer.apply(child.result, sib.result);
- *     }
- *   }
- *   public E getRawResult() { return result; }
- *
- *   public static  E mapReduce(E[] array, MyMapper mapper, MyReducer reducer) {
- *     return new MapReducer(null, array, mapper, reducer,
- *                              0, array.length).invoke();
- *   }
- * }}
- * - * Here, method {@code onCompletion} takes a form common to many - * completion designs that combine results. This callback-style method - * is triggered once per task, in either of the two different contexts - * in which the pending count is, or becomes, zero: (1) by a task - * itself, if its pending count is zero upon invocation of {@code - * tryComplete}, or (2) by any of its subtasks when they complete and - * decrement the pending count to zero. The {@code caller} argument - * distinguishes cases. Most often, when the caller is {@code this}, - * no action is necessary. Otherwise the caller argument can be used - * (usually via a cast) to supply a value (and/or links to other - * values) to be combined. Assuming proper use of pending counts, the - * actions inside {@code onCompletion} occur (once) upon completion of - * a task and its subtasks. No additional synchronization is required - * within this method to ensure thread safety of accesses to fields of - * this task or other completed tasks. - * - *

Completion Traversals. If using {@code onCompletion} to - * process completions is inapplicable or inconvenient, you can use - * methods {@link #firstComplete} and {@link #nextComplete} to create - * custom traversals. For example, to define a MapReducer that only - * splits out right-hand tasks in the form of the third ForEach - * example, the completions must cooperatively reduce along - * unexhausted subtask links, which can be done as follows: - * - *

 {@code
- * class MapReducer extends CountedCompleter { // version 2
- *   final E[] array; final MyMapper mapper;
- *   final MyReducer reducer; final int lo, hi;
- *   MapReducer forks, next; // record subtask forks in list
- *   E result;
- *   MapReducer(CountedCompleter p, E[] array, MyMapper mapper,
- *              MyReducer reducer, int lo, int hi, MapReducer next) {
- *     super(p);
- *     this.array = array; this.mapper = mapper;
- *     this.reducer = reducer; this.lo = lo; this.hi = hi;
- *     this.next = next;
- *   }
- *   public void compute() {
- *     int l = lo,  h = hi;
- *     while (h - l >= 2) {
- *       int mid = (l + h) >>> 1;
- *       addToPendingCount(1);
- *       (forks = new MapReducer(this, array, mapper, reducer, mid, h, forks)).fork();
- *       h = mid;
- *     }
- *     if (h > l)
- *       result = mapper.apply(array[l]);
- *     // process completions by reducing along and advancing subtask links
- *     for (CountedCompleter c = firstComplete(); c != null; c = c.nextComplete()) {
- *       for (MapReducer t = (MapReducer)c, s = t.forks;  s != null; s = t.forks = s.next)
- *         t.result = reducer.apply(t.result, s.result);
- *     }
- *   }
- *   public E getRawResult() { return result; }
- *
- *   public static  E mapReduce(E[] array, MyMapper mapper, MyReducer reducer) {
- *     return new MapReducer(null, array, mapper, reducer,
- *                              0, array.length, null).invoke();
- *   }
- * }}
- * - *

Triggers. Some CountedCompleters are themselves never - * forked, but instead serve as bits of plumbing in other designs; - * including those in which the completion of one or more async tasks - * triggers another async task. For example: - * - *

 {@code
- * class HeaderBuilder extends CountedCompleter<...> { ... }
- * class BodyBuilder extends CountedCompleter<...> { ... }
- * class PacketSender extends CountedCompleter<...> {
- *   PacketSender(...) { super(null, 1); ... } // trigger on second completion
- *   public void compute() { } // never called
- *   public void onCompletion(CountedCompleter caller) { sendPacket(); }
- * }
- * // sample use:
- * PacketSender p = new PacketSender();
- * new HeaderBuilder(p, ...).fork();
- * new BodyBuilder(p, ...).fork();
- * }
- * - * @since 1.8 - * @author Doug Lea - */ -@SuppressWarnings("all") -public abstract class CountedCompleter extends ForkJoinTask { - private static final long serialVersionUID = 5232453752276485070L; - - /** This task's completer, or null if none */ - final CountedCompleter completer; - /** The number of pending tasks until completion */ - volatile int pending; - - /** - * Creates a new CountedCompleter with the given completer - * and initial pending count. - * - * @param completer this task's completer, or {@code null} if none - * @param initialPendingCount the initial pending count - */ - protected CountedCompleter(CountedCompleter completer, - int initialPendingCount) { - this.completer = completer; - this.pending = initialPendingCount; - } - - /** - * Creates a new CountedCompleter with the given completer - * and an initial pending count of zero. - * - * @param completer this task's completer, or {@code null} if none - */ - protected CountedCompleter(CountedCompleter completer) { - this.completer = completer; - } - - /** - * Creates a new CountedCompleter with no completer - * and an initial pending count of zero. - */ - protected CountedCompleter() { - this.completer = null; - } - - /** - * The main computation performed by this task. - */ - public abstract void compute(); - - /** - * Performs an action when method {@link #tryComplete} is invoked - * and the pending count is zero, or when the unconditional - * method {@link #complete} is invoked. By default, this method - * does nothing. You can distinguish cases by checking the - * identity of the given caller argument. If not equal to {@code - * this}, then it is typically a subtask that may contain results - * (and/or links to other results) to combine. - * - * @param caller the task invoking this method (which may - * be this task itself) - */ - public void onCompletion(CountedCompleter caller) { - } - - /** - * Performs an action when method {@link - * #completeExceptionally(Throwable)} is invoked or method {@link - * #compute} throws an exception, and this task has not already - * otherwise completed normally. On entry to this method, this task - * {@link ForkJoinTask#isCompletedAbnormally}. The return value - * of this method controls further propagation: If {@code true} - * and this task has a completer that has not completed, then that - * completer is also completed exceptionally, with the same - * exception as this completer. The default implementation of - * this method does nothing except return {@code true}. - * - * @param ex the exception - * @param caller the task invoking this method (which may - * be this task itself) - * @return {@code true} if this exception should be propagated to this - * task's completer, if one exists - */ - public boolean onExceptionalCompletion(Throwable ex, CountedCompleter caller) { - return true; - } - - /** - * Returns the completer established in this task's constructor, - * or {@code null} if none. - * - * @return the completer - */ - public final CountedCompleter getCompleter() { - return completer; - } - - /** - * Returns the current pending count. - * - * @return the current pending count - */ - public final int getPendingCount() { - return pending; - } - - /** - * Sets the pending count to the given value. - * - * @param count the count - */ - public final void setPendingCount(int count) { - pending = count; - } - - /** - * Adds (atomically) the given value to the pending count. - * - * @param delta the value to add - */ - public final void addToPendingCount(int delta) { - int c; - do {} while (!U.compareAndSwapInt(this, PENDING, c = pending, c+delta)); - } - - /** - * Sets (atomically) the pending count to the given count only if - * it currently holds the given expected value. - * - * @param expected the expected value - * @param count the new value - * @return {@code true} if successful - */ - public final boolean compareAndSetPendingCount(int expected, int count) { - return U.compareAndSwapInt(this, PENDING, expected, count); - } - - /** - * If the pending count is nonzero, (atomically) decrements it. - * - * @return the initial (undecremented) pending count holding on entry - * to this method - */ - public final int decrementPendingCountUnlessZero() { - int c; - do {} while ((c = pending) != 0 && - !U.compareAndSwapInt(this, PENDING, c, c - 1)); - return c; - } - - /** - * Returns the root of the current computation; i.e., this - * task if it has no completer, else its completer's root. - * - * @return the root of the current computation - */ - public final CountedCompleter getRoot() { - CountedCompleter a = this, p; - while ((p = a.completer) != null) - a = p; - return a; - } - - /** - * If the pending count is nonzero, decrements the count; - * otherwise invokes {@link #onCompletion(CountedCompleter)} - * and then similarly tries to complete this task's completer, - * if one exists, else marks this task as complete. - */ - public final void tryComplete() { - CountedCompleter a = this, s = a; - for (int c;;) { - if ((c = a.pending) == 0) { - a.onCompletion(s); - if ((a = (s = a).completer) == null) { - s.quietlyComplete(); - return; - } - } - else if (U.compareAndSwapInt(a, PENDING, c, c - 1)) - return; - } - } - - /** - * Equivalent to {@link #tryComplete} but does not invoke {@link - * #onCompletion(CountedCompleter)} along the completion path: - * If the pending count is nonzero, decrements the count; - * otherwise, similarly tries to complete this task's completer, if - * one exists, else marks this task as complete. This method may be - * useful in cases where {@code onCompletion} should not, or need - * not, be invoked for each completer in a computation. - */ - public final void propagateCompletion() { - CountedCompleter a = this, s = a; - for (int c;;) { - if ((c = a.pending) == 0) { - if ((a = (s = a).completer) == null) { - s.quietlyComplete(); - return; - } - } - else if (U.compareAndSwapInt(a, PENDING, c, c - 1)) - return; - } - } - - /** - * Regardless of pending count, invokes - * {@link #onCompletion(CountedCompleter)}, marks this task as - * complete and further triggers {@link #tryComplete} on this - * task's completer, if one exists. The given rawResult is - * used as an argument to {@link #setRawResult} before invoking - * {@link #onCompletion(CountedCompleter)} or marking this task - * as complete; its value is meaningful only for classes - * overriding {@code setRawResult}. This method does not modify - * the pending count. - * - *

This method may be useful when forcing completion as soon as - * any one (versus all) of several subtask results are obtained. - * However, in the common (and recommended) case in which {@code - * setRawResult} is not overridden, this effect can be obtained - * more simply using {@code quietlyCompleteRoot();}. - * - * @param rawResult the raw result - */ - public void complete(T rawResult) { - CountedCompleter p; - setRawResult(rawResult); - onCompletion(this); - quietlyComplete(); - if ((p = completer) != null) - p.tryComplete(); - } - - - /** - * If this task's pending count is zero, returns this task; - * otherwise decrements its pending count and returns {@code - * null}. This method is designed to be used with {@link - * #nextComplete} in completion traversal loops. - * - * @return this task, if pending count was zero, else {@code null} - */ - public final CountedCompleter firstComplete() { - for (int c;;) { - if ((c = pending) == 0) - return this; - else if (U.compareAndSwapInt(this, PENDING, c, c - 1)) - return null; - } - } - - /** - * If this task does not have a completer, invokes {@link - * ForkJoinTask#quietlyComplete} and returns {@code null}. Or, if - * the completer's pending count is non-zero, decrements that - * pending count and returns {@code null}. Otherwise, returns the - * completer. This method can be used as part of a completion - * traversal loop for homogeneous task hierarchies: - * - *

 {@code
-     * for (CountedCompleter c = firstComplete();
-     *      c != null;
-     *      c = c.nextComplete()) {
-     *   // ... process c ...
-     * }}
- * - * @return the completer, or {@code null} if none - */ - public final CountedCompleter nextComplete() { - CountedCompleter p; - if ((p = completer) != null) - return p.firstComplete(); - else { - quietlyComplete(); - return null; - } - } - - /** - * Equivalent to {@code getRoot().quietlyComplete()}. - */ - public final void quietlyCompleteRoot() { - for (CountedCompleter a = this, p;;) { - if ((p = a.completer) == null) { - a.quietlyComplete(); - return; - } - a = p; - } - } - - /** - * Supports ForkJoinTask exception propagation. - */ - void internalPropagateException(Throwable ex) { - CountedCompleter a = this, s = a; - while (a.onExceptionalCompletion(ex, s) && - (a = (s = a).completer) != null && a.status >= 0 && - a.recordExceptionalCompletion(ex) == EXCEPTIONAL) - ; - } - - /** - * Implements execution conventions for CountedCompleters. - */ - protected final boolean exec() { - compute(); - return false; - } - - /** - * Returns the result of the computation. By default - * returns {@code null}, which is appropriate for {@code Void} - * actions, but in other cases should be overridden, almost - * always to return a field or function of a field that - * holds the result upon completion. - * - * @return the result of the computation - */ - public T getRawResult() { return null; } - - /** - * A method that result-bearing CountedCompleters may optionally - * use to help maintain result data. By default, does nothing. - * Overrides are not recommended. However, if this method is - * overridden to update existing objects or fields, then it must - * in general be defined to be thread-safe. - */ - protected void setRawResult(T t) { } - - // Unsafe mechanics - private static final sun.misc.Unsafe U; - private static final long PENDING; - static { - try { - U = getUnsafe(); - PENDING = U.objectFieldOffset - (CountedCompleter.class.getDeclaredField("pending")); - } catch (Exception e) { - throw new Error(e); - } - } - - /** - * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. - * Replace with a simple call to Unsafe.getUnsafe when integrating - * into a jdk. - * - * @return a sun.misc.Unsafe - */ - private static sun.misc.Unsafe getUnsafe() { - try { - return sun.misc.Unsafe.getUnsafe(); - } catch (SecurityException tryReflectionInstead) {} - try { - return java.security.AccessController.doPrivileged - (new java.security.PrivilegedExceptionAction() { - public sun.misc.Unsafe run() throws Exception { - Class k = sun.misc.Unsafe.class; - for (java.lang.reflect.Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) - return k.cast(x); - } - throw new NoSuchFieldError("the Unsafe"); - }}); - } catch (java.security.PrivilegedActionException e) { - throw new RuntimeException("Could not initialize intrinsics", - e.getCause()); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/internal/chmv8/ForkJoinPool.java b/api/src/main/java/org/asynchttpclient/internal/chmv8/ForkJoinPool.java deleted file mode 100644 index 88d539e038..0000000000 --- a/api/src/main/java/org/asynchttpclient/internal/chmv8/ForkJoinPool.java +++ /dev/null @@ -1,3358 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project 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. - */ - -/* - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -package org.asynchttpclient.internal.chmv8; - -import java.lang.Thread.UncaughtExceptionHandler; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.AbstractExecutorService; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.RunnableFuture; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - -/** - * An {@link ExecutorService} for running {@link ForkJoinTask}s. - * A {@code ForkJoinPool} provides the entry point for submissions - * from non-{@code ForkJoinTask} clients, as well as management and - * monitoring operations. - * - *

A {@code ForkJoinPool} differs from other kinds of {@link - * ExecutorService} mainly by virtue of employing - * work-stealing: all threads in the pool attempt to find and - * execute tasks submitted to the pool and/or created by other active - * tasks (eventually blocking waiting for work if none exist). This - * enables efficient processing when most tasks spawn other subtasks - * (as do most {@code ForkJoinTask}s), as well as when many small - * tasks are submitted to the pool from external clients. Especially - * when setting asyncMode to true in constructors, {@code - * ForkJoinPool}s may also be appropriate for use with event-style - * tasks that are never joined. - * - *

A static {@link #commonPool()} is available and appropriate for - * most applications. The common pool is used by any ForkJoinTask that - * is not explicitly submitted to a specified pool. Using the common - * pool normally reduces resource usage (its threads are slowly - * reclaimed during periods of non-use, and reinstated upon subsequent - * use). - * - *

For applications that require separate or custom pools, a {@code - * ForkJoinPool} may be constructed with a given target parallelism - * level; by default, equal to the number of available processors. The - * pool attempts to maintain enough active (or available) threads by - * dynamically adding, suspending, or resuming internal worker - * threads, even if some tasks are stalled waiting to join others. - * However, no such adjustments are guaranteed in the face of blocked - * I/O or other unmanaged synchronization. The nested {@link - * ManagedBlocker} interface enables extension of the kinds of - * synchronization accommodated. - * - *

In addition to execution and lifecycle control methods, this - * class provides status check methods (for example - * {@link #getStealCount}) that are intended to aid in developing, - * tuning, and monitoring fork/join applications. Also, method - * {@link #toString} returns indications of pool state in a - * convenient form for informal monitoring. - * - *

As is the case with other ExecutorServices, there are three - * main task execution methods summarized in the following table. - * These are designed to be used primarily by clients not already - * engaged in fork/join computations in the current pool. The main - * forms of these methods accept instances of {@code ForkJoinTask}, - * but overloaded forms also allow mixed execution of plain {@code - * Runnable}- or {@code Callable}- based activities as well. However, - * tasks that are already executing in a pool should normally instead - * use the within-computation forms listed in the table unless using - * async event-style tasks that are not usually joined, in which case - * there is little difference among choice of methods. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Summary of task execution methods
Call from non-fork/join clients Call from within fork/join computations
Arrange async execution {@link #execute(ForkJoinTask)} {@link ForkJoinTask#fork}
Await and obtain result {@link #invoke(ForkJoinTask)} {@link ForkJoinTask#invoke}
Arrange exec and obtain Future {@link #submit(ForkJoinTask)} {@link ForkJoinTask#fork} (ForkJoinTasks are Futures)
- * - *

The common pool is by default constructed with default - * parameters, but these may be controlled by setting three - * {@linkplain System#getProperty system properties}: - *

    - *
  • {@code java.util.concurrent.ForkJoinPool.common.parallelism} - * - the parallelism level, a non-negative integer - *
  • {@code java.util.concurrent.ForkJoinPool.common.threadFactory} - * - the class name of a {@link ForkJoinWorkerThreadFactory} - *
  • {@code java.util.concurrent.ForkJoinPool.common.exceptionHandler} - * - the class name of a {@link UncaughtExceptionHandler} - *
- * The system class loader is used to load these classes. - * Upon any error in establishing these settings, default parameters - * are used. It is possible to disable or limit the use of threads in - * the common pool by setting the parallelism property to zero, and/or - * using a factory that may return {@code null}. - * - *

Implementation notes: This implementation restricts the - * maximum number of running threads to 32767. Attempts to create - * pools with greater than the maximum number result in - * {@code IllegalArgumentException}. - * - *

This implementation rejects submitted tasks (that is, by throwing - * {@link RejectedExecutionException}) only when the pool is shut down - * or internal resources have been exhausted. - * - * @since 1.7 - * @author Doug Lea - */ -@SuppressWarnings("all") -public class ForkJoinPool extends AbstractExecutorService { - - /* - * Implementation Overview - * - * This class and its nested classes provide the main - * functionality and control for a set of worker threads: - * Submissions from non-FJ threads enter into submission queues. - * Workers take these tasks and typically split them into subtasks - * that may be stolen by other workers. Preference rules give - * first priority to processing tasks from their own queues (LIFO - * or FIFO, depending on mode), then to randomized FIFO steals of - * tasks in other queues. - * - * WorkQueues - * ========== - * - * Most operations occur within work-stealing queues (in nested - * class WorkQueue). These are special forms of Deques that - * support only three of the four possible end-operations -- push, - * pop, and poll (aka steal), under the further constraints that - * push and pop are called only from the owning thread (or, as - * extended here, under a lock), while poll may be called from - * other threads. (If you are unfamiliar with them, you probably - * want to read Herlihy and Shavit's book "The Art of - * Multiprocessor programming", chapter 16 describing these in - * more detail before proceeding.) The main work-stealing queue - * design is roughly similar to those in the papers "Dynamic - * Circular Work-Stealing Deque" by Chase and Lev, SPAA 2005 - * (http://research.sun.com/scalable/pubs/index.html) and - * "Idempotent work stealing" by Michael, Saraswat, and Vechev, - * PPoPP 2009 (http://portal.acm.org/citation.cfm?id=1504186). - * See also "Correct and Efficient Work-Stealing for Weak Memory - * Models" by Le, Pop, Cohen, and Nardelli, PPoPP 2013 - * (http://www.di.ens.fr/~zappa/readings/ppopp13.pdf) for an - * analysis of memory ordering (atomic, volatile etc) issues. The - * main differences ultimately stem from GC requirements that we - * null out taken slots as soon as we can, to maintain as small a - * footprint as possible even in programs generating huge numbers - * of tasks. To accomplish this, we shift the CAS arbitrating pop - * vs poll (steal) from being on the indices ("base" and "top") to - * the slots themselves. So, both a successful pop and poll - * mainly entail a CAS of a slot from non-null to null. Because - * we rely on CASes of references, we do not need tag bits on base - * or top. They are simple ints as used in any circular - * array-based queue (see for example ArrayDeque). Updates to the - * indices must still be ordered in a way that guarantees that top - * == base means the queue is empty, but otherwise may err on the - * side of possibly making the queue appear nonempty when a push, - * pop, or poll have not fully committed. Note that this means - * that the poll operation, considered individually, is not - * wait-free. One thief cannot successfully continue until another - * in-progress one (or, if previously empty, a push) completes. - * However, in the aggregate, we ensure at least probabilistic - * non-blockingness. If an attempted steal fails, a thief always - * chooses a different random victim target to try next. So, in - * order for one thief to progress, it suffices for any - * in-progress poll or new push on any empty queue to - * complete. (This is why we normally use method pollAt and its - * variants that try once at the apparent base index, else - * consider alternative actions, rather than method poll.) - * - * This approach also enables support of a user mode in which local - * task processing is in FIFO, not LIFO order, simply by using - * poll rather than pop. This can be useful in message-passing - * frameworks in which tasks are never joined. However neither - * mode considers affinities, loads, cache localities, etc, so - * rarely provide the best possible performance on a given - * machine, but portably provide good throughput by averaging over - * these factors. (Further, even if we did try to use such - * information, we do not usually have a basis for exploiting it. - * For example, some sets of tasks profit from cache affinities, - * but others are harmed by cache pollution effects.) - * - * WorkQueues are also used in a similar way for tasks submitted - * to the pool. We cannot mix these tasks in the same queues used - * for work-stealing (this would contaminate lifo/fifo - * processing). Instead, we randomly associate submission queues - * with submitting threads, using a form of hashing. The - * Submitter probe value serves as a hash code for - * choosing existing queues, and may be randomly repositioned upon - * contention with other submitters. In essence, submitters act - * like workers except that they are restricted to executing local - * tasks that they submitted (or in the case of CountedCompleters, - * others with the same root task). However, because most - * shared/external queue operations are more expensive than - * internal, and because, at steady state, external submitters - * will compete for CPU with workers, ForkJoinTask.join and - * related methods disable them from repeatedly helping to process - * tasks if all workers are active. Insertion of tasks in shared - * mode requires a lock (mainly to protect in the case of - * resizing) but we use only a simple spinlock (using bits in - * field qlock), because submitters encountering a busy queue move - * on to try or create other queues -- they block only when - * creating and registering new queues. - * - * Management - * ========== - * - * The main throughput advantages of work-stealing stem from - * decentralized control -- workers mostly take tasks from - * themselves or each other. We cannot negate this in the - * implementation of other management responsibilities. The main - * tactic for avoiding bottlenecks is packing nearly all - * essentially atomic control state into two volatile variables - * that are by far most often read (not written) as status and - * consistency checks. - * - * Field "ctl" contains 64 bits holding all the information needed - * to atomically decide to add, inactivate, enqueue (on an event - * queue), dequeue, and/or re-activate workers. To enable this - * packing, we restrict maximum parallelism to (1<<15)-1 (which is - * far in excess of normal operating range) to allow ids, counts, - * and their negations (used for thresholding) to fit into 16bit - * fields. - * - * Field "plock" is a form of sequence lock with a saturating - * shutdown bit (similarly for per-queue "qlocks"), mainly - * protecting updates to the workQueues array, as well as to - * enable shutdown. When used as a lock, it is normally only very - * briefly held, so is nearly always available after at most a - * brief spin, but we use a monitor-based backup strategy to - * block when needed. - * - * Recording WorkQueues. WorkQueues are recorded in the - * "workQueues" array that is created upon first use and expanded - * if necessary. Updates to the array while recording new workers - * and unrecording terminated ones are protected from each other - * by a lock but the array is otherwise concurrently readable, and - * accessed directly. To simplify index-based operations, the - * array size is always a power of two, and all readers must - * tolerate null slots. Worker queues are at odd indices. Shared - * (submission) queues are at even indices, up to a maximum of 64 - * slots, to limit growth even if array needs to expand to add - * more workers. Grouping them together in this way simplifies and - * speeds up task scanning. - * - * All worker thread creation is on-demand, triggered by task - * submissions, replacement of terminated workers, and/or - * compensation for blocked workers. However, all other support - * code is set up to work with other policies. To ensure that we - * do not hold on to worker references that would prevent GC, ALL - * accesses to workQueues are via indices into the workQueues - * array (which is one source of some of the messy code - * constructions here). In essence, the workQueues array serves as - * a weak reference mechanism. Thus for example the wait queue - * field of ctl stores indices, not references. Access to the - * workQueues in associated methods (for example signalWork) must - * both index-check and null-check the IDs. All such accesses - * ignore bad IDs by returning out early from what they are doing, - * since this can only be associated with termination, in which - * case it is OK to give up. All uses of the workQueues array - * also check that it is non-null (even if previously - * non-null). This allows nulling during termination, which is - * currently not necessary, but remains an option for - * resource-revocation-based shutdown schemes. It also helps - * reduce JIT issuance of uncommon-trap code, which tends to - * unnecessarily complicate control flow in some methods. - * - * Event Queuing. Unlike HPC work-stealing frameworks, we cannot - * let workers spin indefinitely scanning for tasks when none can - * be found immediately, and we cannot start/resume workers unless - * there appear to be tasks available. On the other hand, we must - * quickly prod them into action when new tasks are submitted or - * generated. In many usages, ramp-up time to activate workers is - * the main limiting factor in overall performance (this is - * compounded at program start-up by JIT compilation and - * allocation). So we try to streamline this as much as possible. - * We park/unpark workers after placing in an event wait queue - * when they cannot find work. This "queue" is actually a simple - * Treiber stack, headed by the "id" field of ctl, plus a 15bit - * counter value (that reflects the number of times a worker has - * been inactivated) to avoid ABA effects (we need only as many - * version numbers as worker threads). Successors are held in - * field WorkQueue.nextWait. Queuing deals with several intrinsic - * races, mainly that a task-producing thread can miss seeing (and - * signalling) another thread that gave up looking for work but - * has not yet entered the wait queue. We solve this by requiring - * a full sweep of all workers (via repeated calls to method - * scan()) both before and after a newly waiting worker is added - * to the wait queue. Because enqueued workers may actually be - * rescanning rather than waiting, we set and clear the "parker" - * field of WorkQueues to reduce unnecessary calls to unpark. - * (This requires a secondary recheck to avoid missed signals.) - * Note the unusual conventions about Thread.interrupts - * surrounding parking and other blocking: Because interrupts are - * used solely to alert threads to check termination, which is - * checked anyway upon blocking, we clear status (using - * Thread.interrupted) before any call to park, so that park does - * not immediately return due to status being set via some other - * unrelated call to interrupt in user code. - * - * Signalling. We create or wake up workers only when there - * appears to be at least one task they might be able to find and - * execute. When a submission is added or another worker adds a - * task to a queue that has fewer than two tasks, they signal - * waiting workers (or trigger creation of new ones if fewer than - * the given parallelism level -- signalWork). These primary - * signals are buttressed by others whenever other threads remove - * a task from a queue and notice that there are other tasks there - * as well. So in general, pools will be over-signalled. On most - * platforms, signalling (unpark) overhead time is noticeably - * long, and the time between signalling a thread and it actually - * making progress can be very noticeably long, so it is worth - * offloading these delays from critical paths as much as - * possible. Additionally, workers spin-down gradually, by staying - * alive so long as they see the ctl state changing. Similar - * stability-sensing techniques are also used before blocking in - * awaitJoin and helpComplete. - * - * Trimming workers. To release resources after periods of lack of - * use, a worker starting to wait when the pool is quiescent will - * time out and terminate if the pool has remained quiescent for a - * given period -- a short period if there are more threads than - * parallelism, longer as the number of threads decreases. This - * will slowly propagate, eventually terminating all workers after - * periods of non-use. - * - * Shutdown and Termination. A call to shutdownNow atomically sets - * a plock bit and then (non-atomically) sets each worker's - * qlock status, cancels all unprocessed tasks, and wakes up - * all waiting workers. Detecting whether termination should - * commence after a non-abrupt shutdown() call requires more work - * and bookkeeping. We need consensus about quiescence (i.e., that - * there is no more work). The active count provides a primary - * indication but non-abrupt shutdown still requires a rechecking - * scan for any workers that are inactive but not queued. - * - * Joining Tasks - * ============= - * - * Any of several actions may be taken when one worker is waiting - * to join a task stolen (or always held) by another. Because we - * are multiplexing many tasks on to a pool of workers, we can't - * just let them block (as in Thread.join). We also cannot just - * reassign the joiner's run-time stack with another and replace - * it later, which would be a form of "continuation", that even if - * possible is not necessarily a good idea since we sometimes need - * both an unblocked task and its continuation to progress. - * Instead we combine two tactics: - * - * Helping: Arranging for the joiner to execute some task that it - * would be running if the steal had not occurred. - * - * Compensating: Unless there are already enough live threads, - * method tryCompensate() may create or re-activate a spare - * thread to compensate for blocked joiners until they unblock. - * - * A third form (implemented in tryRemoveAndExec) amounts to - * helping a hypothetical compensator: If we can readily tell that - * a possible action of a compensator is to steal and execute the - * task being joined, the joining thread can do so directly, - * without the need for a compensation thread (although at the - * expense of larger run-time stacks, but the tradeoff is - * typically worthwhile). - * - * The ManagedBlocker extension API can't use helping so relies - * only on compensation in method awaitBlocker. - * - * The algorithm in tryHelpStealer entails a form of "linear" - * helping: Each worker records (in field currentSteal) the most - * recent task it stole from some other worker. Plus, it records - * (in field currentJoin) the task it is currently actively - * joining. Method tryHelpStealer uses these markers to try to - * find a worker to help (i.e., steal back a task from and execute - * it) that could hasten completion of the actively joined task. - * In essence, the joiner executes a task that would be on its own - * local deque had the to-be-joined task not been stolen. This may - * be seen as a conservative variant of the approach in Wagner & - * Calder "Leapfrogging: a portable technique for implementing - * efficient futures" SIGPLAN Notices, 1993 - * (http://portal.acm.org/citation.cfm?id=155354). It differs in - * that: (1) We only maintain dependency links across workers upon - * steals, rather than use per-task bookkeeping. This sometimes - * requires a linear scan of workQueues array to locate stealers, - * but often doesn't because stealers leave hints (that may become - * stale/wrong) of where to locate them. It is only a hint - * because a worker might have had multiple steals and the hint - * records only one of them (usually the most current). Hinting - * isolates cost to when it is needed, rather than adding to - * per-task overhead. (2) It is "shallow", ignoring nesting and - * potentially cyclic mutual steals. (3) It is intentionally - * racy: field currentJoin is updated only while actively joining, - * which means that we miss links in the chain during long-lived - * tasks, GC stalls etc (which is OK since blocking in such cases - * is usually a good idea). (4) We bound the number of attempts - * to find work (see MAX_HELP) and fall back to suspending the - * worker and if necessary replacing it with another. - * - * Helping actions for CountedCompleters are much simpler: Method - * helpComplete can take and execute any task with the same root - * as the task being waited on. However, this still entails some - * traversal of completer chains, so is less efficient than using - * CountedCompleters without explicit joins. - * - * It is impossible to keep exactly the target parallelism number - * of threads running at any given time. Determining the - * existence of conservatively safe helping targets, the - * availability of already-created spares, and the apparent need - * to create new spares are all racy, so we rely on multiple - * retries of each. Compensation in the apparent absence of - * helping opportunities is challenging to control on JVMs, where - * GC and other activities can stall progress of tasks that in - * turn stall out many other dependent tasks, without us being - * able to determine whether they will ever require compensation. - * Even though work-stealing otherwise encounters little - * degradation in the presence of more threads than cores, - * aggressively adding new threads in such cases entails risk of - * unwanted positive feedback control loops in which more threads - * cause more dependent stalls (as well as delayed progress of - * unblocked threads to the point that we know they are available) - * leading to more situations requiring more threads, and so - * on. This aspect of control can be seen as an (analytically - * intractable) game with an opponent that may choose the worst - * (for us) active thread to stall at any time. We take several - * precautions to bound losses (and thus bound gains), mainly in - * methods tryCompensate and awaitJoin. - * - * Common Pool - * =========== - * - * The static common pool always exists after static - * initialization. Since it (or any other created pool) need - * never be used, we minimize initial construction overhead and - * footprint to the setup of about a dozen fields, with no nested - * allocation. Most bootstrapping occurs within method - * fullExternalPush during the first submission to the pool. - * - * When external threads submit to the common pool, they can - * perform subtask processing (see externalHelpJoin and related - * methods). This caller-helps policy makes it sensible to set - * common pool parallelism level to one (or more) less than the - * total number of available cores, or even zero for pure - * caller-runs. We do not need to record whether external - * submissions are to the common pool -- if not, externalHelpJoin - * returns quickly (at the most helping to signal some common pool - * workers). These submitters would otherwise be blocked waiting - * for completion, so the extra effort (with liberally sprinkled - * task status checks) in inapplicable cases amounts to an odd - * form of limited spin-wait before blocking in ForkJoinTask.join. - * - * Style notes - * =========== - * - * There is a lot of representation-level coupling among classes - * ForkJoinPool, ForkJoinWorkerThread, and ForkJoinTask. The - * fields of WorkQueue maintain data structures managed by - * ForkJoinPool, so are directly accessed. There is little point - * trying to reduce this, since any associated future changes in - * representations will need to be accompanied by algorithmic - * changes anyway. Several methods intrinsically sprawl because - * they must accumulate sets of consistent reads of volatiles held - * in local variables. Methods signalWork() and scan() are the - * main bottlenecks, so are especially heavily - * micro-optimized/mangled. There are lots of inline assignments - * (of form "while ((local = field) != 0)") which are usually the - * simplest way to ensure the required read orderings (which are - * sometimes critical). This leads to a "C"-like style of listing - * declarations of these locals at the heads of methods or blocks. - * There are several occurrences of the unusual "do {} while - * (!cas...)" which is the simplest way to force an update of a - * CAS'ed variable. There are also other coding oddities (including - * several unnecessary-looking hoisted null checks) that help - * some methods perform reasonably even when interpreted (not - * compiled). - * - * The order of declarations in this file is: - * (1) Static utility functions - * (2) Nested (static) classes - * (3) Static fields - * (4) Fields, along with constants used when unpacking some of them - * (5) Internal control methods - * (6) Callbacks and other support for ForkJoinTask methods - * (7) Exported methods - * (8) Static block initializing statics in minimally dependent order - */ - - // Static utilities - - /** - * If there is a security manager, makes sure caller has - * permission to modify threads. - */ - private static void checkPermission() { - SecurityManager security = System.getSecurityManager(); - if (security != null) - security.checkPermission(modifyThreadPermission); - } - - // Nested classes - - /** - * Factory for creating new {@link ForkJoinWorkerThread}s. - * A {@code ForkJoinWorkerThreadFactory} must be defined and used - * for {@code ForkJoinWorkerThread} subclasses that extend base - * functionality or initialize threads with different contexts. - */ - public static interface ForkJoinWorkerThreadFactory { - /** - * Returns a new worker thread operating in the given pool. - * - * @param pool the pool this thread works in - * @throws NullPointerException if the pool is null - * @return the new worker thread - */ - public ForkJoinWorkerThread newThread(ForkJoinPool pool); - } - - /** - * Default ForkJoinWorkerThreadFactory implementation; creates a - * new ForkJoinWorkerThread. - */ - static final class DefaultForkJoinWorkerThreadFactory - implements ForkJoinWorkerThreadFactory { - public final ForkJoinWorkerThread newThread(ForkJoinPool pool) { - return new ForkJoinWorkerThread(pool); - } - } - - /** - * Class for artificial tasks that are used to replace the target - * of local joins if they are removed from an interior queue slot - * in WorkQueue.tryRemoveAndExec. We don't need the proxy to - * actually do anything beyond having a unique identity. - */ - static final class EmptyTask extends ForkJoinTask { - private static final long serialVersionUID = -7721805057305804111L; - EmptyTask() { status = ForkJoinTask.NORMAL; } // force done - public final Void getRawResult() { return null; } - public final void setRawResult(Void x) {} - public final boolean exec() { return true; } - } - - /** - * Queues supporting work-stealing as well as external task - * submission. See above for main rationale and algorithms. - * Implementation relies heavily on "Unsafe" intrinsics - * and selective use of "volatile": - * - * Field "base" is the index (mod array.length) of the least valid - * queue slot, which is always the next position to steal (poll) - * from if nonempty. Reads and writes require volatile orderings - * but not CAS, because updates are only performed after slot - * CASes. - * - * Field "top" is the index (mod array.length) of the next queue - * slot to push to or pop from. It is written only by owner thread - * for push, or under lock for external/shared push, and accessed - * by other threads only after reading (volatile) base. Both top - * and base are allowed to wrap around on overflow, but (top - - * base) (or more commonly -(base - top) to force volatile read of - * base before top) still estimates size. The lock ("qlock") is - * forced to -1 on termination, causing all further lock attempts - * to fail. (Note: we don't need CAS for termination state because - * upon pool shutdown, all shared-queues will stop being used - * anyway.) Nearly all lock bodies are set up so that exceptions - * within lock bodies are "impossible" (modulo JVM errors that - * would cause failure anyway.) - * - * The array slots are read and written using the emulation of - * volatiles/atomics provided by Unsafe. Insertions must in - * general use putOrderedObject as a form of releasing store to - * ensure that all writes to the task object are ordered before - * its publication in the queue. All removals entail a CAS to - * null. The array is always a power of two. To ensure safety of - * Unsafe array operations, all accesses perform explicit null - * checks and implicit bounds checks via power-of-two masking. - * - * In addition to basic queuing support, this class contains - * fields described elsewhere to control execution. It turns out - * to work better memory-layout-wise to include them in this class - * rather than a separate class. - * - * Performance on most platforms is very sensitive to placement of - * instances of both WorkQueues and their arrays -- we absolutely - * do not want multiple WorkQueue instances or multiple queue - * arrays sharing cache lines. (It would be best for queue objects - * and their arrays to share, but there is nothing available to - * help arrange that). The @Contended annotation alerts JVMs to - * try to keep instances apart. - */ - static final class WorkQueue { - /** - * Capacity of work-stealing queue array upon initialization. - * Must be a power of two; at least 4, but should be larger to - * reduce or eliminate cacheline sharing among queues. - * Currently, it is much larger, as a partial workaround for - * the fact that JVMs often place arrays in locations that - * share GC bookkeeping (especially cardmarks) such that - * per-write accesses encounter serious memory contention. - */ - static final int INITIAL_QUEUE_CAPACITY = 1 << 13; - - /** - * Maximum size for queue arrays. Must be a power of two less - * than or equal to 1 << (31 - width of array entry) to ensure - * lack of wraparound of index calculations, but defined to a - * value a bit less than this to help users trap runaway - * programs before saturating systems. - */ - static final int MAXIMUM_QUEUE_CAPACITY = 1 << 26; // 64M - - // Heuristic padding to ameliorate unfortunate memory placements - volatile long pad00, pad01, pad02, pad03, pad04, pad05, pad06; - - volatile int eventCount; // encoded inactivation count; < 0 if inactive - int nextWait; // encoded record of next event waiter - int nsteals; // number of steals - int hint; // steal index hint - short poolIndex; // index of this queue in pool - final short mode; // 0: lifo, > 0: fifo, < 0: shared - volatile int qlock; // 1: locked, -1: terminate; else 0 - volatile int base; // index of next slot for poll - int top; // index of next slot for push - ForkJoinTask[] array; // the elements (initially unallocated) - final ForkJoinPool pool; // the containing pool (may be null) - final ForkJoinWorkerThread owner; // owning thread or null if shared - volatile Thread parker; // == owner during call to park; else null - volatile ForkJoinTask currentJoin; // task being joined in awaitJoin - ForkJoinTask currentSteal; // current non-local task being executed - - volatile Object pad10, pad11, pad12, pad13, pad14, pad15, pad16, pad17; - volatile Object pad18, pad19, pad1a, pad1b, pad1c, pad1d; - - WorkQueue(ForkJoinPool pool, ForkJoinWorkerThread owner, int mode, - int seed) { - this.pool = pool; - this.owner = owner; - this.mode = (short)mode; - this.hint = seed; // store initial seed for runWorker - // Place indices in the center of array (that is not yet allocated) - base = top = INITIAL_QUEUE_CAPACITY >>> 1; - } - - /** - * Returns the approximate number of tasks in the queue. - */ - final int queueSize() { - int n = base - top; // non-owner callers must read base first - return (n >= 0) ? 0 : -n; // ignore transient negative - } - - /** - * Provides a more accurate estimate of whether this queue has - * any tasks than does queueSize, by checking whether a - * near-empty queue has at least one unclaimed task. - */ - final boolean isEmpty() { - ForkJoinTask[] a; int m, s; - int n = base - (s = top); - return (n >= 0 || - (n == -1 && - ((a = array) == null || - (m = a.length - 1) < 0 || - U.getObject - (a, (long)((m & (s - 1)) << ASHIFT) + ABASE) == null))); - } - - /** - * Pushes a task. Call only by owner in unshared queues. (The - * shared-queue version is embedded in method externalPush.) - * - * @param task the task. Caller must ensure non-null. - * @throws RejectedExecutionException if array cannot be resized - */ - final void push(ForkJoinTask task) { - ForkJoinTask[] a; ForkJoinPool p; - int s = top, n; - if ((a = array) != null) { // ignore if queue removed - int m = a.length - 1; - U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task); - if ((n = (top = s + 1) - base) <= 2) - (p = pool).signalWork(p.workQueues, this); - else if (n >= m) - growArray(); - } - } - - /** - * Initializes or doubles the capacity of array. Call either - * by owner or with lock held -- it is OK for base, but not - * top, to move while resizings are in progress. - */ - final ForkJoinTask[] growArray() { - ForkJoinTask[] oldA = array; - int size = oldA != null ? oldA.length << 1 : INITIAL_QUEUE_CAPACITY; - if (size > MAXIMUM_QUEUE_CAPACITY) - throw new RejectedExecutionException("Queue capacity exceeded"); - int oldMask, t, b; - ForkJoinTask[] a = array = new ForkJoinTask[size]; - if (oldA != null && (oldMask = oldA.length - 1) >= 0 && - (t = top) - (b = base) > 0) { - int mask = size - 1; - do { - ForkJoinTask x; - int oldj = ((b & oldMask) << ASHIFT) + ABASE; - int j = ((b & mask) << ASHIFT) + ABASE; - x = (ForkJoinTask)U.getObjectVolatile(oldA, oldj); - if (x != null && - U.compareAndSwapObject(oldA, oldj, x, null)) - U.putObjectVolatile(a, j, x); - } while (++b != t); - } - return a; - } - - /** - * Takes next task, if one exists, in LIFO order. Call only - * by owner in unshared queues. - */ - final ForkJoinTask pop() { - ForkJoinTask[] a; ForkJoinTask t; int m; - if ((a = array) != null && (m = a.length - 1) >= 0) { - for (int s; (s = top - 1) - base >= 0;) { - long j = ((m & s) << ASHIFT) + ABASE; - if ((t = (ForkJoinTask)U.getObject(a, j)) == null) - break; - if (U.compareAndSwapObject(a, j, t, null)) { - top = s; - return t; - } - } - } - return null; - } - - /** - * Takes a task in FIFO order if b is base of queue and a task - * can be claimed without contention. Specialized versions - * appear in ForkJoinPool methods scan and tryHelpStealer. - */ - final ForkJoinTask pollAt(int b) { - ForkJoinTask t; ForkJoinTask[] a; - if ((a = array) != null) { - int j = (((a.length - 1) & b) << ASHIFT) + ABASE; - if ((t = (ForkJoinTask)U.getObjectVolatile(a, j)) != null && - base == b && U.compareAndSwapObject(a, j, t, null)) { - U.putOrderedInt(this, QBASE, b + 1); - return t; - } - } - return null; - } - - /** - * Takes next task, if one exists, in FIFO order. - */ - final ForkJoinTask poll() { - ForkJoinTask[] a; int b; ForkJoinTask t; - while ((b = base) - top < 0 && (a = array) != null) { - int j = (((a.length - 1) & b) << ASHIFT) + ABASE; - t = (ForkJoinTask)U.getObjectVolatile(a, j); - if (t != null) { - if (U.compareAndSwapObject(a, j, t, null)) { - U.putOrderedInt(this, QBASE, b + 1); - return t; - } - } - else if (base == b) { - if (b + 1 == top) - break; - Thread.yield(); // wait for lagging update (very rare) - } - } - return null; - } - - /** - * Takes next task, if one exists, in order specified by mode. - */ - final ForkJoinTask nextLocalTask() { - return mode == 0 ? pop() : poll(); - } - - /** - * Returns next task, if one exists, in order specified by mode. - */ - final ForkJoinTask peek() { - ForkJoinTask[] a = array; int m; - if (a == null || (m = a.length - 1) < 0) - return null; - int i = mode == 0 ? top - 1 : base; - int j = ((i & m) << ASHIFT) + ABASE; - return (ForkJoinTask)U.getObjectVolatile(a, j); - } - - /** - * Pops the given task only if it is at the current top. - * (A shared version is available only via FJP.tryExternalUnpush) - */ - final boolean tryUnpush(ForkJoinTask t) { - ForkJoinTask[] a; int s; - if ((a = array) != null && (s = top) != base && - U.compareAndSwapObject - (a, (((a.length - 1) & --s) << ASHIFT) + ABASE, t, null)) { - top = s; - return true; - } - return false; - } - - /** - * Removes and cancels all known tasks, ignoring any exceptions. - */ - final void cancelAll() { - ForkJoinTask.cancelIgnoringExceptions(currentJoin); - ForkJoinTask.cancelIgnoringExceptions(currentSteal); - for (ForkJoinTask t; (t = poll()) != null; ) - ForkJoinTask.cancelIgnoringExceptions(t); - } - - // Specialized execution methods - - /** - * Polls and runs tasks until empty. - */ - final void pollAndExecAll() { - for (ForkJoinTask t; (t = poll()) != null;) - t.doExec(); - } - - /** - * Executes a top-level task and any local tasks remaining - * after execution. - */ - final void runTask(ForkJoinTask task) { - if ((currentSteal = task) != null) { - task.doExec(); - ForkJoinTask[] a = array; - int md = mode; - ++nsteals; - currentSteal = null; - if (md != 0) - pollAndExecAll(); - else if (a != null) { - int s, m = a.length - 1; - while ((s = top - 1) - base >= 0) { - long i = ((m & s) << ASHIFT) + ABASE; - ForkJoinTask t = (ForkJoinTask)U.getObject(a, i); - if (t == null) - break; - if (U.compareAndSwapObject(a, i, t, null)) { - top = s; - t.doExec(); - } - } - } - } - } - - /** - * If present, removes from queue and executes the given task, - * or any other cancelled task. Returns (true) on any CAS - * or consistency check failure so caller can retry. - * - * @return false if no progress can be made, else true - */ - final boolean tryRemoveAndExec(ForkJoinTask task) { - boolean stat; - ForkJoinTask[] a; int m, s, b, n; - if (task != null && (a = array) != null && (m = a.length - 1) >= 0 && - (n = (s = top) - (b = base)) > 0) { - boolean removed = false, empty = true; - stat = true; - for (ForkJoinTask t;;) { // traverse from s to b - long j = ((--s & m) << ASHIFT) + ABASE; - t = (ForkJoinTask)U.getObject(a, j); - if (t == null) // inconsistent length - break; - else if (t == task) { - if (s + 1 == top) { // pop - if (!U.compareAndSwapObject(a, j, task, null)) - break; - top = s; - removed = true; - } - else if (base == b) // replace with proxy - removed = U.compareAndSwapObject(a, j, task, - new EmptyTask()); - break; - } - else if (t.status >= 0) - empty = false; - else if (s + 1 == top) { // pop and throw away - if (U.compareAndSwapObject(a, j, t, null)) - top = s; - break; - } - if (--n == 0) { - if (!empty && base == b) - stat = false; - break; - } - } - if (removed) - task.doExec(); - } - else - stat = false; - return stat; - } - - /** - * Tries to poll for and execute the given task or any other - * task in its CountedCompleter computation. - */ - final boolean pollAndExecCC(CountedCompleter root) { - ForkJoinTask[] a; int b; Object o; CountedCompleter t, r; - if ((b = base) - top < 0 && (a = array) != null) { - long j = (((a.length - 1) & b) << ASHIFT) + ABASE; - if ((o = U.getObjectVolatile(a, j)) == null) - return true; // retry - if (o instanceof CountedCompleter) { - for (t = (CountedCompleter)o, r = t;;) { - if (r == root) { - if (base == b && - U.compareAndSwapObject(a, j, t, null)) { - U.putOrderedInt(this, QBASE, b + 1); - t.doExec(); - } - return true; - } - else if ((r = r.completer) == null) - break; // not part of root computation - } - } - } - return false; - } - - /** - * Tries to pop and execute the given task or any other task - * in its CountedCompleter computation. - */ - final boolean externalPopAndExecCC(CountedCompleter root) { - ForkJoinTask[] a; int s; Object o; CountedCompleter t, r; - if (base - (s = top) < 0 && (a = array) != null) { - long j = (((a.length - 1) & (s - 1)) << ASHIFT) + ABASE; - if ((o = U.getObject(a, j)) instanceof CountedCompleter) { - for (t = (CountedCompleter)o, r = t;;) { - if (r == root) { - if (U.compareAndSwapInt(this, QLOCK, 0, 1)) { - if (top == s && array == a && - U.compareAndSwapObject(a, j, t, null)) { - top = s - 1; - qlock = 0; - t.doExec(); - } - else - qlock = 0; - } - return true; - } - else if ((r = r.completer) == null) - break; - } - } - } - return false; - } - - /** - * Internal version - */ - final boolean internalPopAndExecCC(CountedCompleter root) { - ForkJoinTask[] a; int s; Object o; CountedCompleter t, r; - if (base - (s = top) < 0 && (a = array) != null) { - long j = (((a.length - 1) & (s - 1)) << ASHIFT) + ABASE; - if ((o = U.getObject(a, j)) instanceof CountedCompleter) { - for (t = (CountedCompleter)o, r = t;;) { - if (r == root) { - if (U.compareAndSwapObject(a, j, t, null)) { - top = s - 1; - t.doExec(); - } - return true; - } - else if ((r = r.completer) == null) - break; - } - } - } - return false; - } - - /** - * Returns true if owned and not known to be blocked. - */ - final boolean isApparentlyUnblocked() { - Thread wt; Thread.State s; - return (eventCount >= 0 && - (wt = owner) != null && - (s = wt.getState()) != Thread.State.BLOCKED && - s != Thread.State.WAITING && - s != Thread.State.TIMED_WAITING); - } - - // Unsafe mechanics - private static final sun.misc.Unsafe U; - private static final long QBASE; - private static final long QLOCK; - private static final int ABASE; - private static final int ASHIFT; - static { - try { - U = getUnsafe(); - Class k = WorkQueue.class; - Class ak = ForkJoinTask[].class; - QBASE = U.objectFieldOffset - (k.getDeclaredField("base")); - QLOCK = U.objectFieldOffset - (k.getDeclaredField("qlock")); - ABASE = U.arrayBaseOffset(ak); - int scale = U.arrayIndexScale(ak); - if ((scale & (scale - 1)) != 0) - throw new Error("data type scale not a power of two"); - ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); - } catch (Exception e) { - throw new Error(e); - } - } - } - - // static fields (initialized in static initializer below) - - /** - * Per-thread submission bookkeeping. Shared across all pools - * to reduce ThreadLocal pollution and because random motion - * to avoid contention in one pool is likely to hold for others. - * Lazily initialized on first submission (but null-checked - * in other contexts to avoid unnecessary initialization). - */ - static final ThreadLocal submitters; - - /** - * Creates a new ForkJoinWorkerThread. This factory is used unless - * overridden in ForkJoinPool constructors. - */ - public static final ForkJoinWorkerThreadFactory - defaultForkJoinWorkerThreadFactory; - - /** - * Permission required for callers of methods that may start or - * kill threads. - */ - private static final RuntimePermission modifyThreadPermission; - - /** - * Common (static) pool. Non-null for public use unless a static - * construction exception, but internal usages null-check on use - * to paranoically avoid potential initialization circularities - * as well as to simplify generated code. - */ - static final ForkJoinPool common; - - /** - * Common pool parallelism. To allow simpler use and management - * when common pool threads are disabled, we allow the underlying - * common.parallelism field to be zero, but in that case still report - * parallelism as 1 to reflect resulting caller-runs mechanics. - */ - static final int commonParallelism; - - /** - * Sequence number for creating workerNamePrefix. - */ - private static int poolNumberSequence; - - /** - * Returns the next sequence number. We don't expect this to - * ever contend, so use simple builtin sync. - */ - private static final synchronized int nextPoolId() { - return ++poolNumberSequence; - } - - // static constants - - /** - * Initial timeout value (in nanoseconds) for the thread - * triggering quiescence to park waiting for new work. On timeout, - * the thread will instead try to shrink the number of - * workers. The value should be large enough to avoid overly - * aggressive shrinkage during most transient stalls (long GCs - * etc). - */ - private static final long IDLE_TIMEOUT = 2000L * 1000L * 1000L; // 2sec - - /** - * Timeout value when there are more threads than parallelism level - */ - private static final long FAST_IDLE_TIMEOUT = 200L * 1000L * 1000L; - - /** - * Tolerance for idle timeouts, to cope with timer undershoots - */ - private static final long TIMEOUT_SLOP = 2000000L; - - /** - * The maximum stolen->joining link depth allowed in method - * tryHelpStealer. Must be a power of two. Depths for legitimate - * chains are unbounded, but we use a fixed constant to avoid - * (otherwise unchecked) cycles and to bound staleness of - * traversal parameters at the expense of sometimes blocking when - * we could be helping. - */ - private static final int MAX_HELP = 64; - - /** - * Increment for seed generators. See class ThreadLocal for - * explanation. - */ - private static final int SEED_INCREMENT = 0x61c88647; - - /* - * Bits and masks for control variables - * - * Field ctl is a long packed with: - * AC: Number of active running workers minus target parallelism (16 bits) - * TC: Number of total workers minus target parallelism (16 bits) - * ST: true if pool is terminating (1 bit) - * EC: the wait count of top waiting thread (15 bits) - * ID: poolIndex of top of Treiber stack of waiters (16 bits) - * - * When convenient, we can extract the upper 32 bits of counts and - * the lower 32 bits of queue state, u = (int)(ctl >>> 32) and e = - * (int)ctl. The ec field is never accessed alone, but always - * together with id and st. The offsets of counts by the target - * parallelism and the positionings of fields makes it possible to - * perform the most common checks via sign tests of fields: When - * ac is negative, there are not enough active workers, when tc is - * negative, there are not enough total workers, and when e is - * negative, the pool is terminating. To deal with these possibly - * negative fields, we use casts in and out of "short" and/or - * signed shifts to maintain signedness. - * - * When a thread is queued (inactivated), its eventCount field is - * set negative, which is the only way to tell if a worker is - * prevented from executing tasks, even though it must continue to - * scan for them to avoid queuing races. Note however that - * eventCount updates lag releases so usage requires care. - * - * Field plock is an int packed with: - * SHUTDOWN: true if shutdown is enabled (1 bit) - * SEQ: a sequence lock, with PL_LOCK bit set if locked (30 bits) - * SIGNAL: set when threads may be waiting on the lock (1 bit) - * - * The sequence number enables simple consistency checks: - * Staleness of read-only operations on the workQueues array can - * be checked by comparing plock before vs after the reads. - */ - - // bit positions/shifts for fields - private static final int AC_SHIFT = 48; - private static final int TC_SHIFT = 32; - private static final int ST_SHIFT = 31; - private static final int EC_SHIFT = 16; - - // bounds - private static final int SMASK = 0xffff; // short bits - private static final int MAX_CAP = 0x7fff; // max #workers - 1 - private static final int EVENMASK = 0xfffe; // even short bits - private static final int SQMASK = 0x007e; // max 64 (even) slots - private static final int SHORT_SIGN = 1 << 15; - private static final int INT_SIGN = 1 << 31; - - // masks - private static final long STOP_BIT = 0x0001L << ST_SHIFT; - private static final long AC_MASK = ((long)SMASK) << AC_SHIFT; - private static final long TC_MASK = ((long)SMASK) << TC_SHIFT; - - // units for incrementing and decrementing - private static final long TC_UNIT = 1L << TC_SHIFT; - private static final long AC_UNIT = 1L << AC_SHIFT; - - // masks and units for dealing with u = (int)(ctl >>> 32) - private static final int UAC_SHIFT = AC_SHIFT - 32; - private static final int UTC_SHIFT = TC_SHIFT - 32; - private static final int UAC_MASK = SMASK << UAC_SHIFT; - private static final int UTC_MASK = SMASK << UTC_SHIFT; - private static final int UAC_UNIT = 1 << UAC_SHIFT; - private static final int UTC_UNIT = 1 << UTC_SHIFT; - - // masks and units for dealing with e = (int)ctl - private static final int E_MASK = 0x7fffffff; // no STOP_BIT - private static final int E_SEQ = 1 << EC_SHIFT; - - // plock bits - private static final int SHUTDOWN = 1 << 31; - private static final int PL_LOCK = 2; - private static final int PL_SIGNAL = 1; - private static final int PL_SPINS = 1 << 8; - - // access mode for WorkQueue - static final int LIFO_QUEUE = 0; - static final int FIFO_QUEUE = 1; - static final int SHARED_QUEUE = -1; - - // Heuristic padding to ameliorate unfortunate memory placements - volatile long pad00, pad01, pad02, pad03, pad04, pad05, pad06; - - // Instance fields - volatile long stealCount; // collects worker counts - volatile long ctl; // main pool control - volatile int plock; // shutdown status and seqLock - volatile int indexSeed; // worker/submitter index seed - final short parallelism; // parallelism level - final short mode; // LIFO/FIFO - WorkQueue[] workQueues; // main registry - final ForkJoinWorkerThreadFactory factory; - final UncaughtExceptionHandler ueh; // per-worker UEH - final String workerNamePrefix; // to create worker name string - - volatile Object pad10, pad11, pad12, pad13, pad14, pad15, pad16, pad17; - volatile Object pad18, pad19, pad1a, pad1b; - - /** - * Acquires the plock lock to protect worker array and related - * updates. This method is called only if an initial CAS on plock - * fails. This acts as a spinlock for normal cases, but falls back - * to builtin monitor to block when (rarely) needed. This would be - * a terrible idea for a highly contended lock, but works fine as - * a more conservative alternative to a pure spinlock. - */ - private int acquirePlock() { - int spins = PL_SPINS, ps, nps; - for (;;) { - if (((ps = plock) & PL_LOCK) == 0 && - U.compareAndSwapInt(this, PLOCK, ps, nps = ps + PL_LOCK)) - return nps; - else if (spins >= 0) { - if (ThreadLocalRandom.current().nextInt() >= 0) - --spins; - } - else if (U.compareAndSwapInt(this, PLOCK, ps, ps | PL_SIGNAL)) { - synchronized (this) { - if ((plock & PL_SIGNAL) != 0) { - try { - wait(); - } catch (InterruptedException ie) { - try { - Thread.currentThread().interrupt(); - } catch (SecurityException ignore) { - } - } - } - else - notifyAll(); - } - } - } - } - - /** - * Unlocks and signals any thread waiting for plock. Called only - * when CAS of seq value for unlock fails. - */ - private void releasePlock(int ps) { - plock = ps; - synchronized (this) { notifyAll(); } - } - - /** - * Tries to create and start one worker if fewer than target - * parallelism level exist. Adjusts counts etc on failure. - */ - private void tryAddWorker() { - long c; int u, e; - while ((u = (int)((c = ctl) >>> 32)) < 0 && - (u & SHORT_SIGN) != 0 && (e = (int)c) >= 0) { - long nc = ((long)(((u + UTC_UNIT) & UTC_MASK) | - ((u + UAC_UNIT) & UAC_MASK)) << 32) | (long)e; - if (U.compareAndSwapLong(this, CTL, c, nc)) { - ForkJoinWorkerThreadFactory fac; - Throwable ex = null; - ForkJoinWorkerThread wt = null; - try { - if ((fac = factory) != null && - (wt = fac.newThread(this)) != null) { - wt.start(); - break; - } - } catch (Throwable rex) { - ex = rex; - } - deregisterWorker(wt, ex); - break; - } - } - } - - // Registering and deregistering workers - - /** - * Callback from ForkJoinWorkerThread to establish and record its - * WorkQueue. To avoid scanning bias due to packing entries in - * front of the workQueues array, we treat the array as a simple - * power-of-two hash table using per-thread seed as hash, - * expanding as needed. - * - * @param wt the worker thread - * @return the worker's queue - */ - final WorkQueue registerWorker(ForkJoinWorkerThread wt) { - UncaughtExceptionHandler handler; WorkQueue[] ws; int s, ps; - wt.setDaemon(true); - if ((handler = ueh) != null) - wt.setUncaughtExceptionHandler(handler); - do {} while (!U.compareAndSwapInt(this, INDEXSEED, s = indexSeed, - s += SEED_INCREMENT) || - s == 0); // skip 0 - WorkQueue w = new WorkQueue(this, wt, mode, s); - if (((ps = plock) & PL_LOCK) != 0 || - !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) - ps = acquirePlock(); - int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN); - try { - if ((ws = workQueues) != null) { // skip if shutting down - int n = ws.length, m = n - 1; - int r = (s << 1) | 1; // use odd-numbered indices - if (ws[r &= m] != null) { // collision - int probes = 0; // step by approx half size - int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2; - while (ws[r = (r + step) & m] != null) { - if (++probes >= n) { - workQueues = ws = Arrays.copyOf(ws, n <<= 1); - m = n - 1; - probes = 0; - } - } - } - w.poolIndex = (short)r; - w.eventCount = r; // volatile write orders - ws[r] = w; - } - } finally { - if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) - releasePlock(nps); - } - wt.setName(workerNamePrefix.concat(Integer.toString(w.poolIndex >>> 1))); - return w; - } - - /** - * Final callback from terminating worker, as well as upon failure - * to construct or start a worker. Removes record of worker from - * array, and adjusts counts. If pool is shutting down, tries to - * complete termination. - * - * @param wt the worker thread, or null if construction failed - * @param ex the exception causing failure, or null if none - */ - final void deregisterWorker(ForkJoinWorkerThread wt, Throwable ex) { - WorkQueue w = null; - if (wt != null && (w = wt.workQueue) != null) { - int ps; long sc; - w.qlock = -1; // ensure set - do {} while (!U.compareAndSwapLong(this, STEALCOUNT, - sc = stealCount, - sc + w.nsteals)); - if (((ps = plock) & PL_LOCK) != 0 || - !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) - ps = acquirePlock(); - int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN); - try { - int idx = w.poolIndex; - WorkQueue[] ws = workQueues; - if (ws != null && idx >= 0 && idx < ws.length && ws[idx] == w) - ws[idx] = null; - } finally { - if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) - releasePlock(nps); - } - } - - long c; // adjust ctl counts - do {} while (!U.compareAndSwapLong - (this, CTL, c = ctl, (((c - AC_UNIT) & AC_MASK) | - ((c - TC_UNIT) & TC_MASK) | - (c & ~(AC_MASK|TC_MASK))))); - - if (!tryTerminate(false, false) && w != null && w.array != null) { - w.cancelAll(); // cancel remaining tasks - WorkQueue[] ws; WorkQueue v; Thread p; int u, i, e; - while ((u = (int)((c = ctl) >>> 32)) < 0 && (e = (int)c) >= 0) { - if (e > 0) { // activate or create replacement - if ((ws = workQueues) == null || - (i = e & SMASK) >= ws.length || - (v = ws[i]) == null) - break; - long nc = (((long)(v.nextWait & E_MASK)) | - ((long)(u + UAC_UNIT) << 32)); - if (v.eventCount != (e | INT_SIGN)) - break; - if (U.compareAndSwapLong(this, CTL, c, nc)) { - v.eventCount = (e + E_SEQ) & E_MASK; - if ((p = v.parker) != null) - U.unpark(p); - break; - } - } - else { - if ((short)u < 0) - tryAddWorker(); - break; - } - } - } - if (ex == null) // help clean refs on way out - ForkJoinTask.helpExpungeStaleExceptions(); - else // rethrow - ForkJoinTask.rethrow(ex); - } - - // Submissions - - /** - * Per-thread records for threads that submit to pools. Currently - * holds only pseudo-random seed / index that is used to choose - * submission queues in method externalPush. In the future, this may - * also incorporate a means to implement different task rejection - * and resubmission policies. - * - * Seeds for submitters and workers/workQueues work in basically - * the same way but are initialized and updated using slightly - * different mechanics. Both are initialized using the same - * approach as in class ThreadLocal, where successive values are - * unlikely to collide with previous values. Seeds are then - * randomly modified upon collisions using xorshifts, which - * requires a non-zero seed. - */ - static final class Submitter { - int seed; - Submitter(int s) { seed = s; } - } - - /** - * Unless shutting down, adds the given task to a submission queue - * at submitter's current queue index (modulo submission - * range). Only the most common path is directly handled in this - * method. All others are relayed to fullExternalPush. - * - * @param task the task. Caller must ensure non-null. - */ - final void externalPush(ForkJoinTask task) { - Submitter z = submitters.get(); - WorkQueue q; int r, m, s, n, am; ForkJoinTask[] a; - int ps = plock; - WorkQueue[] ws = workQueues; - if (z != null && ps > 0 && ws != null && (m = (ws.length - 1)) >= 0 && - (q = ws[m & (r = z.seed) & SQMASK]) != null && r != 0 && - U.compareAndSwapInt(q, QLOCK, 0, 1)) { // lock - if ((a = q.array) != null && - (am = a.length - 1) > (n = (s = q.top) - q.base)) { - int j = ((am & s) << ASHIFT) + ABASE; - U.putOrderedObject(a, j, task); - q.top = s + 1; // push on to deque - q.qlock = 0; - if (n <= 1) - signalWork(ws, q); - return; - } - q.qlock = 0; - } - fullExternalPush(task); - } - - /** - * Full version of externalPush. This method is called, among - * other times, upon the first submission of the first task to the - * pool, so must perform secondary initialization. It also - * detects first submission by an external thread by looking up - * its ThreadLocal, and creates a new shared queue if the one at - * index if empty or contended. The plock lock body must be - * exception-free (so no try/finally) so we optimistically - * allocate new queues outside the lock and throw them away if - * (very rarely) not needed. - * - * Secondary initialization occurs when plock is zero, to create - * workQueue array and set plock to a valid value. This lock body - * must also be exception-free. Because the plock seq value can - * eventually wrap around zero, this method harmlessly fails to - * reinitialize if workQueues exists, while still advancing plock. - */ - private void fullExternalPush(ForkJoinTask task) { - int r = 0; // random index seed - for (Submitter z = submitters.get();;) { - WorkQueue[] ws; WorkQueue q; int ps, m, k; - if (z == null) { - if (U.compareAndSwapInt(this, INDEXSEED, r = indexSeed, - r += SEED_INCREMENT) && r != 0) - submitters.set(z = new Submitter(r)); - } - else if (r == 0) { // move to a different index - r = z.seed; - r ^= r << 13; // same xorshift as WorkQueues - r ^= r >>> 17; - z.seed = r ^= (r << 5); - } - if ((ps = plock) < 0) - throw new RejectedExecutionException(); - else if (ps == 0 || (ws = workQueues) == null || - (m = ws.length - 1) < 0) { // initialize workQueues - int p = parallelism; // find power of two table size - int n = (p > 1) ? p - 1 : 1; // ensure at least 2 slots - n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; - n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1; - WorkQueue[] nws = ((ws = workQueues) == null || ws.length == 0 ? - new WorkQueue[n] : null); - if (((ps = plock) & PL_LOCK) != 0 || - !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) - ps = acquirePlock(); - if (((ws = workQueues) == null || ws.length == 0) && nws != null) - workQueues = nws; - int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN); - if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) - releasePlock(nps); - } - else if ((q = ws[k = r & m & SQMASK]) != null) { - if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) { - ForkJoinTask[] a = q.array; - int s = q.top; - boolean submitted = false; - try { // locked version of push - if ((a != null && a.length > s + 1 - q.base) || - (a = q.growArray()) != null) { // must presize - int j = (((a.length - 1) & s) << ASHIFT) + ABASE; - U.putOrderedObject(a, j, task); - q.top = s + 1; - submitted = true; - } - } finally { - q.qlock = 0; // unlock - } - if (submitted) { - signalWork(ws, q); - return; - } - } - r = 0; // move on failure - } - else if (((ps = plock) & PL_LOCK) == 0) { // create new queue - q = new WorkQueue(this, null, SHARED_QUEUE, r); - q.poolIndex = (short)k; - if (((ps = plock) & PL_LOCK) != 0 || - !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) - ps = acquirePlock(); - if ((ws = workQueues) != null && k < ws.length && ws[k] == null) - ws[k] = q; - int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN); - if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) - releasePlock(nps); - } - else - r = 0; - } - } - - // Maintaining ctl counts - - /** - * Increments active count; mainly called upon return from blocking. - */ - final void incrementActiveCount() { - long c; - do {} while (!U.compareAndSwapLong - (this, CTL, c = ctl, ((c & ~AC_MASK) | - ((c & AC_MASK) + AC_UNIT)))); - } - - /** - * Tries to create or activate a worker if too few are active. - * - * @param ws the worker array to use to find signallees - * @param q if non-null, the queue holding tasks to be processed - */ - final void signalWork(WorkQueue[] ws, WorkQueue q) { - for (;;) { - long c; int e, u, i; WorkQueue w; Thread p; - if ((u = (int)((c = ctl) >>> 32)) >= 0) - break; - if ((e = (int)c) <= 0) { - if ((short)u < 0) - tryAddWorker(); - break; - } - if (ws == null || ws.length <= (i = e & SMASK) || - (w = ws[i]) == null) - break; - long nc = (((long)(w.nextWait & E_MASK)) | - ((long)(u + UAC_UNIT)) << 32); - int ne = (e + E_SEQ) & E_MASK; - if (w.eventCount == (e | INT_SIGN) && - U.compareAndSwapLong(this, CTL, c, nc)) { - w.eventCount = ne; - if ((p = w.parker) != null) - U.unpark(p); - break; - } - if (q != null && q.base >= q.top) - break; - } - } - - // Scanning for tasks - - /** - * Top-level runloop for workers, called by ForkJoinWorkerThread.run. - */ - final void runWorker(WorkQueue w) { - w.growArray(); // allocate queue - for (int r = w.hint; scan(w, r) == 0; ) { - r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift - } - } - - /** - * Scans for and, if found, runs one task, else possibly - * inactivates the worker. This method operates on single reads of - * volatile state and is designed to be re-invoked continuously, - * in part because it returns upon detecting inconsistencies, - * contention, or state changes that indicate possible success on - * re-invocation. - * - * The scan searches for tasks across queues starting at a random - * index, checking each at least twice. The scan terminates upon - * either finding a non-empty queue, or completing the sweep. If - * the worker is not inactivated, it takes and runs a task from - * this queue. Otherwise, if not activated, it tries to activate - * itself or some other worker by signalling. On failure to find a - * task, returns (for retry) if pool state may have changed during - * an empty scan, or tries to inactivate if active, else possibly - * blocks or terminates via method awaitWork. - * - * @param w the worker (via its WorkQueue) - * @param r a random seed - * @return worker qlock status if would have waited, else 0 - */ - private final int scan(WorkQueue w, int r) { - WorkQueue[] ws; int m; - long c = ctl; // for consistency check - if ((ws = workQueues) != null && (m = ws.length - 1) >= 0 && w != null) { - for (int j = m + m + 1, ec = w.eventCount;;) { - WorkQueue q; int b, e; ForkJoinTask[] a; ForkJoinTask t; - if ((q = ws[(r - j) & m]) != null && - (b = q.base) - q.top < 0 && (a = q.array) != null) { - long i = (((a.length - 1) & b) << ASHIFT) + ABASE; - if ((t = ((ForkJoinTask) - U.getObjectVolatile(a, i))) != null) { - if (ec < 0) - helpRelease(c, ws, w, q, b); - else if (q.base == b && - U.compareAndSwapObject(a, i, t, null)) { - U.putOrderedInt(q, QBASE, b + 1); - if ((b + 1) - q.top < 0) - signalWork(ws, q); - w.runTask(t); - } - } - break; - } - else if (--j < 0) { - if ((ec | (e = (int)c)) < 0) // inactive or terminating - return awaitWork(w, c, ec); - else if (ctl == c) { // try to inactivate and enqueue - long nc = (long)ec | ((c - AC_UNIT) & (AC_MASK|TC_MASK)); - w.nextWait = e; - w.eventCount = ec | INT_SIGN; - if (!U.compareAndSwapLong(this, CTL, c, nc)) - w.eventCount = ec; // back out - } - break; - } - } - } - return 0; - } - - /** - * A continuation of scan(), possibly blocking or terminating - * worker w. Returns without blocking if pool state has apparently - * changed since last invocation. Also, if inactivating w has - * caused the pool to become quiescent, checks for pool - * termination, and, so long as this is not the only worker, waits - * for event for up to a given duration. On timeout, if ctl has - * not changed, terminates the worker, which will in turn wake up - * another worker to possibly repeat this process. - * - * @param w the calling worker - * @param c the ctl value on entry to scan - * @param ec the worker's eventCount on entry to scan - */ - private final int awaitWork(WorkQueue w, long c, int ec) { - int stat, ns; long parkTime, deadline; - if ((stat = w.qlock) >= 0 && w.eventCount == ec && ctl == c && - !Thread.interrupted()) { - int e = (int)c; - int u = (int)(c >>> 32); - int d = (u >> UAC_SHIFT) + parallelism; // active count - - if (e < 0 || (d <= 0 && tryTerminate(false, false))) - stat = w.qlock = -1; // pool is terminating - else if ((ns = w.nsteals) != 0) { // collect steals and retry - long sc; - w.nsteals = 0; - do {} while (!U.compareAndSwapLong(this, STEALCOUNT, - sc = stealCount, sc + ns)); - } - else { - long pc = ((d > 0 || ec != (e | INT_SIGN)) ? 0L : - ((long)(w.nextWait & E_MASK)) | // ctl to restore - ((long)(u + UAC_UNIT)) << 32); - if (pc != 0L) { // timed wait if last waiter - int dc = -(short)(c >>> TC_SHIFT); - parkTime = (dc < 0 ? FAST_IDLE_TIMEOUT: - (dc + 1) * IDLE_TIMEOUT); - deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP; - } - else - parkTime = deadline = 0L; - if (w.eventCount == ec && ctl == c) { - Thread wt = Thread.currentThread(); - U.putObject(wt, PARKBLOCKER, this); - w.parker = wt; // emulate LockSupport.park - if (w.eventCount == ec && ctl == c) - U.park(false, parkTime); // must recheck before park - w.parker = null; - U.putObject(wt, PARKBLOCKER, null); - if (parkTime != 0L && ctl == c && - deadline - System.nanoTime() <= 0L && - U.compareAndSwapLong(this, CTL, c, pc)) - stat = w.qlock = -1; // shrink pool - } - } - } - return stat; - } - - /** - * Possibly releases (signals) a worker. Called only from scan() - * when a worker with apparently inactive status finds a non-empty - * queue. This requires revalidating all of the associated state - * from caller. - */ - private final void helpRelease(long c, WorkQueue[] ws, WorkQueue w, - WorkQueue q, int b) { - WorkQueue v; int e, i; Thread p; - if (w != null && w.eventCount < 0 && (e = (int)c) > 0 && - ws != null && ws.length > (i = e & SMASK) && - (v = ws[i]) != null && ctl == c) { - long nc = (((long)(v.nextWait & E_MASK)) | - ((long)((int)(c >>> 32) + UAC_UNIT)) << 32); - int ne = (e + E_SEQ) & E_MASK; - if (q != null && q.base == b && w.eventCount < 0 && - v.eventCount == (e | INT_SIGN) && - U.compareAndSwapLong(this, CTL, c, nc)) { - v.eventCount = ne; - if ((p = v.parker) != null) - U.unpark(p); - } - } - } - - /** - * Tries to locate and execute tasks for a stealer of the given - * task, or in turn one of its stealers, Traces currentSteal -> - * currentJoin links looking for a thread working on a descendant - * of the given task and with a non-empty queue to steal back and - * execute tasks from. The first call to this method upon a - * waiting join will often entail scanning/search, (which is OK - * because the joiner has nothing better to do), but this method - * leaves hints in workers to speed up subsequent calls. The - * implementation is very branchy to cope with potential - * inconsistencies or loops encountering chains that are stale, - * unknown, or so long that they are likely cyclic. - * - * @param joiner the joining worker - * @param task the task to join - * @return 0 if no progress can be made, negative if task - * known complete, else positive - */ - private int tryHelpStealer(WorkQueue joiner, ForkJoinTask task) { - int stat = 0, steps = 0; // bound to avoid cycles - if (task != null && joiner != null && - joiner.base - joiner.top >= 0) { // hoist checks - restart: for (;;) { - ForkJoinTask subtask = task; // current target - for (WorkQueue j = joiner, v;;) { // v is stealer of subtask - WorkQueue[] ws; int m, s, h; - if ((s = task.status) < 0) { - stat = s; - break restart; - } - if ((ws = workQueues) == null || (m = ws.length - 1) <= 0) - break restart; // shutting down - if ((v = ws[h = (j.hint | 1) & m]) == null || - v.currentSteal != subtask) { - for (int origin = h;;) { // find stealer - if (((h = (h + 2) & m) & 15) == 1 && - (subtask.status < 0 || j.currentJoin != subtask)) - continue restart; // occasional staleness check - if ((v = ws[h]) != null && - v.currentSteal == subtask) { - j.hint = h; // save hint - break; - } - if (h == origin) - break restart; // cannot find stealer - } - } - for (;;) { // help stealer or descend to its stealer - ForkJoinTask[] a; int b; - if (subtask.status < 0) // surround probes with - continue restart; // consistency checks - if ((b = v.base) - v.top < 0 && (a = v.array) != null) { - int i = (((a.length - 1) & b) << ASHIFT) + ABASE; - ForkJoinTask t = - (ForkJoinTask)U.getObjectVolatile(a, i); - if (subtask.status < 0 || j.currentJoin != subtask || - v.currentSteal != subtask) - continue restart; // stale - stat = 1; // apparent progress - if (v.base == b) { - if (t == null) - break restart; - if (U.compareAndSwapObject(a, i, t, null)) { - U.putOrderedInt(v, QBASE, b + 1); - ForkJoinTask ps = joiner.currentSteal; - int jt = joiner.top; - do { - joiner.currentSteal = t; - t.doExec(); // clear local tasks too - } while (task.status >= 0 && - joiner.top != jt && - (t = joiner.pop()) != null); - joiner.currentSteal = ps; - break restart; - } - } - } - else { // empty -- try to descend - ForkJoinTask next = v.currentJoin; - if (subtask.status < 0 || j.currentJoin != subtask || - v.currentSteal != subtask) - continue restart; // stale - else if (next == null || ++steps == MAX_HELP) - break restart; // dead-end or maybe cyclic - else { - subtask = next; - j = v; - break; - } - } - } - } - } - } - return stat; - } - - /** - * Analog of tryHelpStealer for CountedCompleters. Tries to steal - * and run tasks within the target's computation. - * - * @param task the task to join - */ - private int helpComplete(WorkQueue joiner, CountedCompleter task) { - WorkQueue[] ws; int m; - int s = 0; - if ((ws = workQueues) != null && (m = ws.length - 1) >= 0 && - joiner != null && task != null) { - int j = joiner.poolIndex; - int scans = m + m + 1; - long c = 0L; // for stability check - for (int k = scans; ; j += 2) { - WorkQueue q; - if ((s = task.status) < 0) - break; - else if (joiner.internalPopAndExecCC(task)) - k = scans; - else if ((s = task.status) < 0) - break; - else if ((q = ws[j & m]) != null && q.pollAndExecCC(task)) - k = scans; - else if (--k < 0) { - if (c == (c = ctl)) - break; - k = scans; - } - } - } - return s; - } - - /** - * Tries to decrement active count (sometimes implicitly) and - * possibly release or create a compensating worker in preparation - * for blocking. Fails on contention or termination. Otherwise, - * adds a new thread if no idle workers are available and pool - * may become starved. - * - * @param c the assumed ctl value - */ - final boolean tryCompensate(long c) { - WorkQueue[] ws = workQueues; - int pc = parallelism, e = (int)c, m, tc; - if (ws != null && (m = ws.length - 1) >= 0 && e >= 0 && ctl == c) { - WorkQueue w = ws[e & m]; - if (e != 0 && w != null) { - Thread p; - long nc = ((long)(w.nextWait & E_MASK) | - (c & (AC_MASK|TC_MASK))); - int ne = (e + E_SEQ) & E_MASK; - if (w.eventCount == (e | INT_SIGN) && - U.compareAndSwapLong(this, CTL, c, nc)) { - w.eventCount = ne; - if ((p = w.parker) != null) - U.unpark(p); - return true; // replace with idle worker - } - } - else if ((tc = (short)(c >>> TC_SHIFT)) >= 0 && - (int)(c >> AC_SHIFT) + pc > 1) { - long nc = ((c - AC_UNIT) & AC_MASK) | (c & ~AC_MASK); - if (U.compareAndSwapLong(this, CTL, c, nc)) - return true; // no compensation - } - else if (tc + pc < MAX_CAP) { - long nc = ((c + TC_UNIT) & TC_MASK) | (c & ~TC_MASK); - if (U.compareAndSwapLong(this, CTL, c, nc)) { - ForkJoinWorkerThreadFactory fac; - Throwable ex = null; - ForkJoinWorkerThread wt = null; - try { - if ((fac = factory) != null && - (wt = fac.newThread(this)) != null) { - wt.start(); - return true; - } - } catch (Throwable rex) { - ex = rex; - } - deregisterWorker(wt, ex); // clean up and return false - } - } - } - return false; - } - - /** - * Helps and/or blocks until the given task is done. - * - * @param joiner the joining worker - * @param task the task - * @return task status on exit - */ - final int awaitJoin(WorkQueue joiner, ForkJoinTask task) { - int s = 0; - if (task != null && (s = task.status) >= 0 && joiner != null) { - ForkJoinTask prevJoin = joiner.currentJoin; - joiner.currentJoin = task; - do {} while (joiner.tryRemoveAndExec(task) && // process local tasks - (s = task.status) >= 0); - if (s >= 0 && (task instanceof CountedCompleter)) - s = helpComplete(joiner, (CountedCompleter)task); - long cc = 0; // for stability checks - while (s >= 0 && (s = task.status) >= 0) { - if ((s = tryHelpStealer(joiner, task)) == 0 && - (s = task.status) >= 0) { - if (!tryCompensate(cc)) - cc = ctl; - else { - if (task.trySetSignal() && (s = task.status) >= 0) { - synchronized (task) { - if (task.status >= 0) { - try { // see ForkJoinTask - task.wait(); // for explanation - } catch (InterruptedException ie) { - } - } - else - task.notifyAll(); - } - } - long c; // reactivate - do {} while (!U.compareAndSwapLong - (this, CTL, c = ctl, - ((c & ~AC_MASK) | - ((c & AC_MASK) + AC_UNIT)))); - } - } - } - joiner.currentJoin = prevJoin; - } - return s; - } - - /** - * Stripped-down variant of awaitJoin used by timed joins. Tries - * to help join only while there is continuous progress. (Caller - * will then enter a timed wait.) - * - * @param joiner the joining worker - * @param task the task - */ - final void helpJoinOnce(WorkQueue joiner, ForkJoinTask task) { - int s; - if (joiner != null && task != null && (s = task.status) >= 0) { - ForkJoinTask prevJoin = joiner.currentJoin; - joiner.currentJoin = task; - do {} while (joiner.tryRemoveAndExec(task) && // process local tasks - (s = task.status) >= 0); - if (s >= 0) { - if (task instanceof CountedCompleter) - helpComplete(joiner, (CountedCompleter)task); - do {} while (task.status >= 0 && - tryHelpStealer(joiner, task) > 0); - } - joiner.currentJoin = prevJoin; - } - } - - /** - * Returns a (probably) non-empty steal queue, if one is found - * during a scan, else null. This method must be retried by - * caller if, by the time it tries to use the queue, it is empty. - */ - private WorkQueue findNonEmptyStealQueue() { - int r = ThreadLocalRandom.current().nextInt(); - for (;;) { - int ps = plock, m; WorkQueue[] ws; WorkQueue q; - if ((ws = workQueues) != null && (m = ws.length - 1) >= 0) { - for (int j = (m + 1) << 2; j >= 0; --j) { - if ((q = ws[(((r - j) << 1) | 1) & m]) != null && - q.base - q.top < 0) - return q; - } - } - if (plock == ps) - return null; - } - } - - /** - * Runs tasks until {@code isQuiescent()}. We piggyback on - * active count ctl maintenance, but rather than blocking - * when tasks cannot be found, we rescan until all others cannot - * find tasks either. - */ - final void helpQuiescePool(WorkQueue w) { - ForkJoinTask ps = w.currentSteal; - for (boolean active = true;;) { - long c; WorkQueue q; ForkJoinTask t; int b; - while ((t = w.nextLocalTask()) != null) - t.doExec(); - if ((q = findNonEmptyStealQueue()) != null) { - if (!active) { // re-establish active count - active = true; - do {} while (!U.compareAndSwapLong - (this, CTL, c = ctl, - ((c & ~AC_MASK) | - ((c & AC_MASK) + AC_UNIT)))); - } - if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null) { - (w.currentSteal = t).doExec(); - w.currentSteal = ps; - } - } - else if (active) { // decrement active count without queuing - long nc = ((c = ctl) & ~AC_MASK) | ((c & AC_MASK) - AC_UNIT); - if ((int)(nc >> AC_SHIFT) + parallelism == 0) - break; // bypass decrement-then-increment - if (U.compareAndSwapLong(this, CTL, c, nc)) - active = false; - } - else if ((int)((c = ctl) >> AC_SHIFT) + parallelism <= 0 && - U.compareAndSwapLong - (this, CTL, c, ((c & ~AC_MASK) | - ((c & AC_MASK) + AC_UNIT)))) - break; - } - } - - /** - * Gets and removes a local or stolen task for the given worker. - * - * @return a task, if available - */ - final ForkJoinTask nextTaskFor(WorkQueue w) { - for (ForkJoinTask t;;) { - WorkQueue q; int b; - if ((t = w.nextLocalTask()) != null) - return t; - if ((q = findNonEmptyStealQueue()) == null) - return null; - if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null) - return t; - } - } - - /** - * Returns a cheap heuristic guide for task partitioning when - * programmers, frameworks, tools, or languages have little or no - * idea about task granularity. In essence by offering this - * method, we ask users only about tradeoffs in overhead vs - * expected throughput and its variance, rather than how finely to - * partition tasks. - * - * In a steady state strict (tree-structured) computation, each - * thread makes available for stealing enough tasks for other - * threads to remain active. Inductively, if all threads play by - * the same rules, each thread should make available only a - * constant number of tasks. - * - * The minimum useful constant is just 1. But using a value of 1 - * would require immediate replenishment upon each steal to - * maintain enough tasks, which is infeasible. Further, - * partitionings/granularities of offered tasks should minimize - * steal rates, which in general means that threads nearer the top - * of computation tree should generate more than those nearer the - * bottom. In perfect steady state, each thread is at - * approximately the same level of computation tree. However, - * producing extra tasks amortizes the uncertainty of progress and - * diffusion assumptions. - * - * So, users will want to use values larger (but not much larger) - * than 1 to both smooth over transient shortages and hedge - * against uneven progress; as traded off against the cost of - * extra task overhead. We leave the user to pick a threshold - * value to compare with the results of this call to guide - * decisions, but recommend values such as 3. - * - * When all threads are active, it is on average OK to estimate - * surplus strictly locally. In steady-state, if one thread is - * maintaining say 2 surplus tasks, then so are others. So we can - * just use estimated queue length. However, this strategy alone - * leads to serious mis-estimates in some non-steady-state - * conditions (ramp-up, ramp-down, other stalls). We can detect - * many of these by further considering the number of "idle" - * threads, that are known to have zero queued tasks, so - * compensate by a factor of (#idle/#active) threads. - * - * Note: The approximation of #busy workers as #active workers is - * not very good under current signalling scheme, and should be - * improved. - */ - static int getSurplusQueuedTaskCount() { - Thread t; ForkJoinWorkerThread wt; ForkJoinPool pool; WorkQueue q; - if (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)) { - int p = (pool = (wt = (ForkJoinWorkerThread)t).pool).parallelism; - int n = (q = wt.workQueue).top - q.base; - int a = (int)(pool.ctl >> AC_SHIFT) + p; - return n - (a > (p >>>= 1) ? 0 : - a > (p >>>= 1) ? 1 : - a > (p >>>= 1) ? 2 : - a > (p >>>= 1) ? 4 : - 8); - } - return 0; - } - - // Termination - - /** - * Possibly initiates and/or completes termination. The caller - * triggering termination runs three passes through workQueues: - * (0) Setting termination status, followed by wakeups of queued - * workers; (1) cancelling all tasks; (2) interrupting lagging - * threads (likely in external tasks, but possibly also blocked in - * joins). Each pass repeats previous steps because of potential - * lagging thread creation. - * - * @param now if true, unconditionally terminate, else only - * if no work and no active workers - * @param enable if true, enable shutdown when next possible - * @return true if now terminating or terminated - */ - private boolean tryTerminate(boolean now, boolean enable) { - int ps; - if (this == common) // cannot shut down - return false; - if ((ps = plock) >= 0) { // enable by setting plock - if (!enable) - return false; - if ((ps & PL_LOCK) != 0 || - !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) - ps = acquirePlock(); - int nps = ((ps + PL_LOCK) & ~SHUTDOWN) | SHUTDOWN; - if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) - releasePlock(nps); - } - for (long c;;) { - if (((c = ctl) & STOP_BIT) != 0) { // already terminating - if ((short)(c >>> TC_SHIFT) + parallelism <= 0) { - synchronized (this) { - notifyAll(); // signal when 0 workers - } - } - return true; - } - if (!now) { // check if idle & no tasks - WorkQueue[] ws; WorkQueue w; - if ((int)(c >> AC_SHIFT) + parallelism > 0) - return false; - if ((ws = workQueues) != null) { - for (int i = 0; i < ws.length; ++i) { - if ((w = ws[i]) != null && - (!w.isEmpty() || - ((i & 1) != 0 && w.eventCount >= 0))) { - signalWork(ws, w); - return false; - } - } - } - } - if (U.compareAndSwapLong(this, CTL, c, c | STOP_BIT)) { - for (int pass = 0; pass < 3; ++pass) { - WorkQueue[] ws; WorkQueue w; Thread wt; - if ((ws = workQueues) != null) { - int n = ws.length; - for (int i = 0; i < n; ++i) { - if ((w = ws[i]) != null) { - w.qlock = -1; - if (pass > 0) { - w.cancelAll(); - if (pass > 1 && (wt = w.owner) != null) { - if (!wt.isInterrupted()) { - try { - wt.interrupt(); - } catch (Throwable ignore) { - } - } - U.unpark(wt); - } - } - } - } - // Wake up workers parked on event queue - int i, e; long cc; Thread p; - while ((e = (int)(cc = ctl) & E_MASK) != 0 && - (i = e & SMASK) < n && i >= 0 && - (w = ws[i]) != null) { - long nc = ((long)(w.nextWait & E_MASK) | - ((cc + AC_UNIT) & AC_MASK) | - (cc & (TC_MASK|STOP_BIT))); - if (w.eventCount == (e | INT_SIGN) && - U.compareAndSwapLong(this, CTL, cc, nc)) { - w.eventCount = (e + E_SEQ) & E_MASK; - w.qlock = -1; - if ((p = w.parker) != null) - U.unpark(p); - } - } - } - } - } - } - } - - // external operations on common pool - - /** - * Returns common pool queue for a thread that has submitted at - * least one task. - */ - static WorkQueue commonSubmitterQueue() { - Submitter z; ForkJoinPool p; WorkQueue[] ws; int m, r; - return ((z = submitters.get()) != null && - (p = common) != null && - (ws = p.workQueues) != null && - (m = ws.length - 1) >= 0) ? - ws[m & z.seed & SQMASK] : null; - } - - /** - * Tries to pop the given task from submitter's queue in common pool. - */ - final boolean tryExternalUnpush(ForkJoinTask task) { - WorkQueue joiner; ForkJoinTask[] a; int m, s; - Submitter z = submitters.get(); - WorkQueue[] ws = workQueues; - boolean popped = false; - if (z != null && ws != null && (m = ws.length - 1) >= 0 && - (joiner = ws[z.seed & m & SQMASK]) != null && - joiner.base != (s = joiner.top) && - (a = joiner.array) != null) { - long j = (((a.length - 1) & (s - 1)) << ASHIFT) + ABASE; - if (U.getObject(a, j) == task && - U.compareAndSwapInt(joiner, QLOCK, 0, 1)) { - if (joiner.top == s && joiner.array == a && - U.compareAndSwapObject(a, j, task, null)) { - joiner.top = s - 1; - popped = true; - } - joiner.qlock = 0; - } - } - return popped; - } - - final int externalHelpComplete(CountedCompleter task) { - WorkQueue joiner; int m, j; - Submitter z = submitters.get(); - WorkQueue[] ws = workQueues; - int s = 0; - if (z != null && ws != null && (m = ws.length - 1) >= 0 && - (joiner = ws[(j = z.seed) & m & SQMASK]) != null && task != null) { - int scans = m + m + 1; - long c = 0L; // for stability check - j |= 1; // poll odd queues - for (int k = scans; ; j += 2) { - WorkQueue q; - if ((s = task.status) < 0) - break; - else if (joiner.externalPopAndExecCC(task)) - k = scans; - else if ((s = task.status) < 0) - break; - else if ((q = ws[j & m]) != null && q.pollAndExecCC(task)) - k = scans; - else if (--k < 0) { - if (c == (c = ctl)) - break; - k = scans; - } - } - } - return s; - } - - // Exported methods - - // Constructors - - /** - * Creates a {@code ForkJoinPool} with parallelism equal to {@link - * java.lang.Runtime#availableProcessors}, using the {@linkplain - * #defaultForkJoinWorkerThreadFactory default thread factory}, - * no UncaughtExceptionHandler, and non-async LIFO processing mode. - * - * @throws SecurityException if a security manager exists and - * the caller is not permitted to modify threads - * because it does not hold {@link - * java.lang.RuntimePermission}{@code ("modifyThread")} - */ - public ForkJoinPool() { - this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()), - defaultForkJoinWorkerThreadFactory, null, false); - } - - /** - * Creates a {@code ForkJoinPool} with the indicated parallelism - * level, the {@linkplain - * #defaultForkJoinWorkerThreadFactory default thread factory}, - * no UncaughtExceptionHandler, and non-async LIFO processing mode. - * - * @param parallelism the parallelism level - * @throws IllegalArgumentException if parallelism less than or - * equal to zero, or greater than implementation limit - * @throws SecurityException if a security manager exists and - * the caller is not permitted to modify threads - * because it does not hold {@link - * java.lang.RuntimePermission}{@code ("modifyThread")} - */ - public ForkJoinPool(int parallelism) { - this(parallelism, defaultForkJoinWorkerThreadFactory, null, false); - } - - /** - * Creates a {@code ForkJoinPool} with the given parameters. - * - * @param parallelism the parallelism level. For default value, - * use {@link java.lang.Runtime#availableProcessors}. - * @param factory the factory for creating new threads. For default value, - * use {@link #defaultForkJoinWorkerThreadFactory}. - * @param handler the handler for internal worker threads that - * terminate due to unrecoverable errors encountered while executing - * tasks. For default value, use {@code null}. - * @param asyncMode if true, - * establishes local first-in-first-out scheduling mode for forked - * tasks that are never joined. This mode may be more appropriate - * than default locally stack-based mode in applications in which - * worker threads only process event-style asynchronous tasks. - * For default value, use {@code false}. - * @throws IllegalArgumentException if parallelism less than or - * equal to zero, or greater than implementation limit - * @throws NullPointerException if the factory is null - * @throws SecurityException if a security manager exists and - * the caller is not permitted to modify threads - * because it does not hold {@link - * java.lang.RuntimePermission}{@code ("modifyThread")} - */ - public ForkJoinPool(int parallelism, - ForkJoinWorkerThreadFactory factory, - UncaughtExceptionHandler handler, - boolean asyncMode) { - this(checkParallelism(parallelism), - checkFactory(factory), - handler, - (asyncMode ? FIFO_QUEUE : LIFO_QUEUE), - "ForkJoinPool-" + nextPoolId() + "-worker-"); - checkPermission(); - } - - private static int checkParallelism(int parallelism) { - if (parallelism <= 0 || parallelism > MAX_CAP) - throw new IllegalArgumentException(); - return parallelism; - } - - private static ForkJoinWorkerThreadFactory checkFactory - (ForkJoinWorkerThreadFactory factory) { - if (factory == null) - throw new NullPointerException(); - return factory; - } - - /** - * Creates a {@code ForkJoinPool} with the given parameters, without - * any security checks or parameter validation. Invoked directly by - * makeCommonPool. - */ - private ForkJoinPool(int parallelism, - ForkJoinWorkerThreadFactory factory, - UncaughtExceptionHandler handler, - int mode, - String workerNamePrefix) { - this.workerNamePrefix = workerNamePrefix; - this.factory = factory; - this.ueh = handler; - this.mode = (short)mode; - this.parallelism = (short)parallelism; - long np = (long)(-parallelism); // offset ctl counts - this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK); - } - - /** - * Returns the common pool instance. This pool is statically - * constructed; its run state is unaffected by attempts to {@link - * #shutdown} or {@link #shutdownNow}. However this pool and any - * ongoing processing are automatically terminated upon program - * {@link System#exit}. Any program that relies on asynchronous - * task processing to complete before program termination should - * invoke {@code commonPool().}{@link #awaitQuiescence awaitQuiescence}, - * before exit. - * - * @return the common pool instance - * @since 1.8 - */ - public static ForkJoinPool commonPool() { - // assert common != null : "static init error"; - return common; - } - - // Execution methods - - /** - * Performs the given task, returning its result upon completion. - * If the computation encounters an unchecked Exception or Error, - * it is rethrown as the outcome of this invocation. Rethrown - * exceptions behave in the same way as regular exceptions, but, - * when possible, contain stack traces (as displayed for example - * using {@code ex.printStackTrace()}) of both the current thread - * as well as the thread actually encountering the exception; - * minimally only the latter. - * - * @param task the task - * @return the task's result - * @throws NullPointerException if the task is null - * @throws RejectedExecutionException if the task cannot be - * scheduled for execution - */ - public T invoke(ForkJoinTask task) { - if (task == null) - throw new NullPointerException(); - externalPush(task); - return task.join(); - } - - /** - * Arranges for (asynchronous) execution of the given task. - * - * @param task the task - * @throws NullPointerException if the task is null - * @throws RejectedExecutionException if the task cannot be - * scheduled for execution - */ - public void execute(ForkJoinTask task) { - if (task == null) - throw new NullPointerException(); - externalPush(task); - } - - // AbstractExecutorService methods - - /** - * @throws NullPointerException if the task is null - * @throws RejectedExecutionException if the task cannot be - * scheduled for execution - */ - public void execute(Runnable task) { - if (task == null) - throw new NullPointerException(); - ForkJoinTask job; - if (task instanceof ForkJoinTask) // avoid re-wrap - job = (ForkJoinTask) task; - else - job = new ForkJoinTask.RunnableExecuteAction(task); - externalPush(job); - } - - /** - * Submits a ForkJoinTask for execution. - * - * @param task the task to submit - * @return the task - * @throws NullPointerException if the task is null - * @throws RejectedExecutionException if the task cannot be - * scheduled for execution - */ - public ForkJoinTask submit(ForkJoinTask task) { - if (task == null) - throw new NullPointerException(); - externalPush(task); - return task; - } - - /** - * @throws NullPointerException if the task is null - * @throws RejectedExecutionException if the task cannot be - * scheduled for execution - */ - public ForkJoinTask submit(Callable task) { - ForkJoinTask job = new ForkJoinTask.AdaptedCallable(task); - externalPush(job); - return job; - } - - /** - * @throws NullPointerException if the task is null - * @throws RejectedExecutionException if the task cannot be - * scheduled for execution - */ - public ForkJoinTask submit(Runnable task, T result) { - ForkJoinTask job = new ForkJoinTask.AdaptedRunnable(task, result); - externalPush(job); - return job; - } - - /** - * @throws NullPointerException if the task is null - * @throws RejectedExecutionException if the task cannot be - * scheduled for execution - */ - public ForkJoinTask submit(Runnable task) { - if (task == null) - throw new NullPointerException(); - ForkJoinTask job; - if (task instanceof ForkJoinTask) // avoid re-wrap - job = (ForkJoinTask) task; - else - job = new ForkJoinTask.AdaptedRunnableAction(task); - externalPush(job); - return job; - } - - /** - * @throws NullPointerException {@inheritDoc} - * @throws RejectedExecutionException {@inheritDoc} - */ - public List> invokeAll(Collection> tasks) { - // In previous versions of this class, this method constructed - // a task to run ForkJoinTask.invokeAll, but now external - // invocation of multiple tasks is at least as efficient. - ArrayList> futures = new ArrayList>(tasks.size()); - - boolean done = false; - try { - for (Callable t : tasks) { - ForkJoinTask f = new ForkJoinTask.AdaptedCallable(t); - futures.add(f); - externalPush(f); - } - for (int i = 0, size = futures.size(); i < size; i++) - ((ForkJoinTask)futures.get(i)).quietlyJoin(); - done = true; - return futures; - } finally { - if (!done) - for (int i = 0, size = futures.size(); i < size; i++) - futures.get(i).cancel(false); - } - } - - /** - * Returns the factory used for constructing new workers. - * - * @return the factory used for constructing new workers - */ - public ForkJoinWorkerThreadFactory getFactory() { - return factory; - } - - /** - * Returns the handler for internal worker threads that terminate - * due to unrecoverable errors encountered while executing tasks. - * - * @return the handler, or {@code null} if none - */ - public UncaughtExceptionHandler getUncaughtExceptionHandler() { - return ueh; - } - - /** - * Returns the targeted parallelism level of this pool. - * - * @return the targeted parallelism level of this pool - */ - public int getParallelism() { - int par; - return ((par = parallelism) > 0) ? par : 1; - } - - /** - * Returns the targeted parallelism level of the common pool. - * - * @return the targeted parallelism level of the common pool - * @since 1.8 - */ - public static int getCommonPoolParallelism() { - return commonParallelism; - } - - /** - * Returns the number of worker threads that have started but not - * yet terminated. The result returned by this method may differ - * from {@link #getParallelism} when threads are created to - * maintain parallelism when others are cooperatively blocked. - * - * @return the number of worker threads - */ - public int getPoolSize() { - return parallelism + (short)(ctl >>> TC_SHIFT); - } - - /** - * Returns {@code true} if this pool uses local first-in-first-out - * scheduling mode for forked tasks that are never joined. - * - * @return {@code true} if this pool uses async mode - */ - public boolean getAsyncMode() { - return mode == FIFO_QUEUE; - } - - /** - * Returns an estimate of the number of worker threads that are - * not blocked waiting to join tasks or for other managed - * synchronization. This method may overestimate the - * number of running threads. - * - * @return the number of worker threads - */ - public int getRunningThreadCount() { - int rc = 0; - WorkQueue[] ws; WorkQueue w; - if ((ws = workQueues) != null) { - for (int i = 1; i < ws.length; i += 2) { - if ((w = ws[i]) != null && w.isApparentlyUnblocked()) - ++rc; - } - } - return rc; - } - - /** - * Returns an estimate of the number of threads that are currently - * stealing or executing tasks. This method may overestimate the - * number of active threads. - * - * @return the number of active threads - */ - public int getActiveThreadCount() { - int r = parallelism + (int)(ctl >> AC_SHIFT); - return (r <= 0) ? 0 : r; // suppress momentarily negative values - } - - /** - * Returns {@code true} if all worker threads are currently idle. - * An idle worker is one that cannot obtain a task to execute - * because none are available to steal from other threads, and - * there are no pending submissions to the pool. This method is - * conservative; it might not return {@code true} immediately upon - * idleness of all threads, but will eventually become true if - * threads remain inactive. - * - * @return {@code true} if all threads are currently idle - */ - public boolean isQuiescent() { - return parallelism + (int)(ctl >> AC_SHIFT) <= 0; - } - - /** - * Returns an estimate of the total number of tasks stolen from - * one thread's work queue by another. The reported value - * underestimates the actual total number of steals when the pool - * is not quiescent. This value may be useful for monitoring and - * tuning fork/join programs: in general, steal counts should be - * high enough to keep threads busy, but low enough to avoid - * overhead and contention across threads. - * - * @return the number of steals - */ - public long getStealCount() { - long count = stealCount; - WorkQueue[] ws; WorkQueue w; - if ((ws = workQueues) != null) { - for (int i = 1; i < ws.length; i += 2) { - if ((w = ws[i]) != null) - count += w.nsteals; - } - } - return count; - } - - /** - * Returns an estimate of the total number of tasks currently held - * in queues by worker threads (but not including tasks submitted - * to the pool that have not begun executing). This value is only - * an approximation, obtained by iterating across all threads in - * the pool. This method may be useful for tuning task - * granularities. - * - * @return the number of queued tasks - */ - public long getQueuedTaskCount() { - long count = 0; - WorkQueue[] ws; WorkQueue w; - if ((ws = workQueues) != null) { - for (int i = 1; i < ws.length; i += 2) { - if ((w = ws[i]) != null) - count += w.queueSize(); - } - } - return count; - } - - /** - * Returns an estimate of the number of tasks submitted to this - * pool that have not yet begun executing. This method may take - * time proportional to the number of submissions. - * - * @return the number of queued submissions - */ - public int getQueuedSubmissionCount() { - int count = 0; - WorkQueue[] ws; WorkQueue w; - if ((ws = workQueues) != null) { - for (int i = 0; i < ws.length; i += 2) { - if ((w = ws[i]) != null) - count += w.queueSize(); - } - } - return count; - } - - /** - * Returns {@code true} if there are any tasks submitted to this - * pool that have not yet begun executing. - * - * @return {@code true} if there are any queued submissions - */ - public boolean hasQueuedSubmissions() { - WorkQueue[] ws; WorkQueue w; - if ((ws = workQueues) != null) { - for (int i = 0; i < ws.length; i += 2) { - if ((w = ws[i]) != null && !w.isEmpty()) - return true; - } - } - return false; - } - - /** - * Removes and returns the next unexecuted submission if one is - * available. This method may be useful in extensions to this - * class that re-assign work in systems with multiple pools. - * - * @return the next submission, or {@code null} if none - */ - protected ForkJoinTask pollSubmission() { - WorkQueue[] ws; WorkQueue w; ForkJoinTask t; - if ((ws = workQueues) != null) { - for (int i = 0; i < ws.length; i += 2) { - if ((w = ws[i]) != null && (t = w.poll()) != null) - return t; - } - } - return null; - } - - /** - * Removes all available unexecuted submitted and forked tasks - * from scheduling queues and adds them to the given collection, - * without altering their execution status. These may include - * artificially generated or wrapped tasks. This method is - * designed to be invoked only when the pool is known to be - * quiescent. Invocations at other times may not remove all - * tasks. A failure encountered while attempting to add elements - * to collection {@code c} may result in elements being in - * neither, either or both collections when the associated - * exception is thrown. The behavior of this operation is - * undefined if the specified collection is modified while the - * operation is in progress. - * - * @param c the collection to transfer elements into - * @return the number of elements transferred - */ - protected int drainTasksTo(Collection> c) { - int count = 0; - WorkQueue[] ws; WorkQueue w; ForkJoinTask t; - if ((ws = workQueues) != null) { - for (int i = 0; i < ws.length; ++i) { - if ((w = ws[i]) != null) { - while ((t = w.poll()) != null) { - c.add(t); - ++count; - } - } - } - } - return count; - } - - /** - * Returns a string identifying this pool, as well as its state, - * including indications of run state, parallelism level, and - * worker and task counts. - * - * @return a string identifying this pool, as well as its state - */ - public String toString() { - // Use a single pass through workQueues to collect counts - long qt = 0L, qs = 0L; int rc = 0; - long st = stealCount; - long c = ctl; - WorkQueue[] ws; WorkQueue w; - if ((ws = workQueues) != null) { - for (int i = 0; i < ws.length; ++i) { - if ((w = ws[i]) != null) { - int size = w.queueSize(); - if ((i & 1) == 0) - qs += size; - else { - qt += size; - st += w.nsteals; - if (w.isApparentlyUnblocked()) - ++rc; - } - } - } - } - int pc = parallelism; - int tc = pc + (short)(c >>> TC_SHIFT); - int ac = pc + (int)(c >> AC_SHIFT); - if (ac < 0) // ignore transient negative - ac = 0; - String level; - if ((c & STOP_BIT) != 0) - level = (tc == 0) ? "Terminated" : "Terminating"; - else - level = plock < 0 ? "Shutting down" : "Running"; - return super.toString() + - "[" + level + - ", parallelism = " + pc + - ", size = " + tc + - ", active = " + ac + - ", running = " + rc + - ", steals = " + st + - ", tasks = " + qt + - ", submissions = " + qs + - "]"; - } - - /** - * Possibly initiates an orderly shutdown in which previously - * submitted tasks are executed, but no new tasks will be - * accepted. Invocation has no effect on execution state if this - * is the {@link #commonPool()}, and no additional effect if - * already shut down. Tasks that are in the process of being - * submitted concurrently during the course of this method may or - * may not be rejected. - * - * @throws SecurityException if a security manager exists and - * the caller is not permitted to modify threads - * because it does not hold {@link - * java.lang.RuntimePermission}{@code ("modifyThread")} - */ - public void shutdown() { - checkPermission(); - tryTerminate(false, true); - } - - /** - * Possibly attempts to cancel and/or stop all tasks, and reject - * all subsequently submitted tasks. Invocation has no effect on - * execution state if this is the {@link #commonPool()}, and no - * additional effect if already shut down. Otherwise, tasks that - * are in the process of being submitted or executed concurrently - * during the course of this method may or may not be - * rejected. This method cancels both existing and unexecuted - * tasks, in order to permit termination in the presence of task - * dependencies. So the method always returns an empty list - * (unlike the case for some other Executors). - * - * @return an empty list - * @throws SecurityException if a security manager exists and - * the caller is not permitted to modify threads - * because it does not hold {@link - * java.lang.RuntimePermission}{@code ("modifyThread")} - */ - public List shutdownNow() { - checkPermission(); - tryTerminate(true, true); - return Collections.emptyList(); - } - - /** - * Returns {@code true} if all tasks have completed following shut down. - * - * @return {@code true} if all tasks have completed following shut down - */ - public boolean isTerminated() { - long c = ctl; - return ((c & STOP_BIT) != 0L && - (short)(c >>> TC_SHIFT) + parallelism <= 0); - } - - /** - * Returns {@code true} if the process of termination has - * commenced but not yet completed. This method may be useful for - * debugging. A return of {@code true} reported a sufficient - * period after shutdown may indicate that submitted tasks have - * ignored or suppressed interruption, or are waiting for I/O, - * causing this executor not to properly terminate. (See the - * advisory notes for class {@link ForkJoinTask} stating that - * tasks should not normally entail blocking operations. But if - * they do, they must abort them on interrupt.) - * - * @return {@code true} if terminating but not yet terminated - */ - public boolean isTerminating() { - long c = ctl; - return ((c & STOP_BIT) != 0L && - (short)(c >>> TC_SHIFT) + parallelism > 0); - } - - /** - * Returns {@code true} if this pool has been shut down. - * - * @return {@code true} if this pool has been shut down - */ - public boolean isShutdown() { - return plock < 0; - } - - /** - * Blocks until all tasks have completed execution after a - * shutdown request, or the timeout occurs, or the current thread - * is interrupted, whichever happens first. Because the {@link - * #commonPool()} never terminates until program shutdown, when - * applied to the common pool, this method is equivalent to {@link - * #awaitQuiescence(long, TimeUnit)} but always returns {@code false}. - * - * @param timeout the maximum time to wait - * @param unit the time unit of the timeout argument - * @return {@code true} if this executor terminated and - * {@code false} if the timeout elapsed before termination - * @throws InterruptedException if interrupted while waiting - */ - public boolean awaitTermination(long timeout, TimeUnit unit) - throws InterruptedException { - if (Thread.interrupted()) - throw new InterruptedException(); - if (this == common) { - awaitQuiescence(timeout, unit); - return false; - } - long nanos = unit.toNanos(timeout); - if (isTerminated()) - return true; - if (nanos <= 0L) - return false; - long deadline = System.nanoTime() + nanos; - synchronized (this) { - for (;;) { - if (isTerminated()) - return true; - if (nanos <= 0L) - return false; - long millis = TimeUnit.NANOSECONDS.toMillis(nanos); - wait(millis > 0L ? millis : 1L); - nanos = deadline - System.nanoTime(); - } - } - } - - /** - * If called by a ForkJoinTask operating in this pool, equivalent - * in effect to {@link ForkJoinTask#helpQuiesce}. Otherwise, - * waits and/or attempts to assist performing tasks until this - * pool {@link #isQuiescent} or the indicated timeout elapses. - * - * @param timeout the maximum time to wait - * @param unit the time unit of the timeout argument - * @return {@code true} if quiescent; {@code false} if the - * timeout elapsed. - */ - public boolean awaitQuiescence(long timeout, TimeUnit unit) { - long nanos = unit.toNanos(timeout); - ForkJoinWorkerThread wt; - Thread thread = Thread.currentThread(); - if ((thread instanceof ForkJoinWorkerThread) && - (wt = (ForkJoinWorkerThread)thread).pool == this) { - helpQuiescePool(wt.workQueue); - return true; - } - long startTime = System.nanoTime(); - WorkQueue[] ws; - int r = 0, m; - boolean found = true; - while (!isQuiescent() && (ws = workQueues) != null && - (m = ws.length - 1) >= 0) { - if (!found) { - if ((System.nanoTime() - startTime) > nanos) - return false; - Thread.yield(); // cannot block - } - found = false; - for (int j = (m + 1) << 2; j >= 0; --j) { - ForkJoinTask t; WorkQueue q; int b; - if ((q = ws[r++ & m]) != null && (b = q.base) - q.top < 0) { - found = true; - if ((t = q.pollAt(b)) != null) - t.doExec(); - break; - } - } - } - return true; - } - - /** - * Waits and/or attempts to assist performing tasks indefinitely - * until the {@link #commonPool()} {@link #isQuiescent}. - */ - static void quiesceCommonPool() { - common.awaitQuiescence(Long.MAX_VALUE, TimeUnit.NANOSECONDS); - } - - /** - * Interface for extending managed parallelism for tasks running - * in {@link ForkJoinPool}s. - * - *

A {@code ManagedBlocker} provides two methods. Method - * {@code isReleasable} must return {@code true} if blocking is - * not necessary. Method {@code block} blocks the current thread - * if necessary (perhaps internally invoking {@code isReleasable} - * before actually blocking). These actions are performed by any - * thread invoking {@link ForkJoinPool#managedBlock(ManagedBlocker)}. - * The unusual methods in this API accommodate synchronizers that - * may, but don't usually, block for long periods. Similarly, they - * allow more efficient internal handling of cases in which - * additional workers may be, but usually are not, needed to - * ensure sufficient parallelism. Toward this end, - * implementations of method {@code isReleasable} must be amenable - * to repeated invocation. - * - *

For example, here is a ManagedBlocker based on a - * ReentrantLock: - *

 {@code
-     * class ManagedLocker implements ManagedBlocker {
-     *   final ReentrantLock lock;
-     *   boolean hasLock = false;
-     *   ManagedLocker(ReentrantLock lock) { this.lock = lock; }
-     *   public boolean block() {
-     *     if (!hasLock)
-     *       lock.lock();
-     *     return true;
-     *   }
-     *   public boolean isReleasable() {
-     *     return hasLock || (hasLock = lock.tryLock());
-     *   }
-     * }}
- * - *

Here is a class that possibly blocks waiting for an - * item on a given queue: - *

 {@code
-     * class QueueTaker implements ManagedBlocker {
-     *   final BlockingQueue queue;
-     *   volatile E item = null;
-     *   QueueTaker(BlockingQueue q) { this.queue = q; }
-     *   public boolean block() throws InterruptedException {
-     *     if (item == null)
-     *       item = queue.take();
-     *     return true;
-     *   }
-     *   public boolean isReleasable() {
-     *     return item != null || (item = queue.poll()) != null;
-     *   }
-     *   public E getItem() { // call after pool.managedBlock completes
-     *     return item;
-     *   }
-     * }}
- */ - public static interface ManagedBlocker { - /** - * Possibly blocks the current thread, for example waiting for - * a lock or condition. - * - * @return {@code true} if no additional blocking is necessary - * (i.e., if isReleasable would return true) - * @throws InterruptedException if interrupted while waiting - * (the method is not required to do so, but is allowed to) - */ - boolean block() throws InterruptedException; - - /** - * Returns {@code true} if blocking is unnecessary. - * @return {@code true} if blocking is unnecessary - */ - boolean isReleasable(); - } - - /** - * Blocks in accord with the given blocker. If the current thread - * is a {@link ForkJoinWorkerThread}, this method possibly - * arranges for a spare thread to be activated if necessary to - * ensure sufficient parallelism while the current thread is blocked. - * - *

If the caller is not a {@link ForkJoinTask}, this method is - * behaviorally equivalent to - *

 {@code
-     * while (!blocker.isReleasable())
-     *   if (blocker.block())
-     *     return;
-     * }
- * - * If the caller is a {@code ForkJoinTask}, then the pool may - * first be expanded to ensure parallelism, and later adjusted. - * - * @param blocker the blocker - * @throws InterruptedException if blocker.block did so - */ - public static void managedBlock(ManagedBlocker blocker) - throws InterruptedException { - Thread t = Thread.currentThread(); - if (t instanceof ForkJoinWorkerThread) { - ForkJoinPool p = ((ForkJoinWorkerThread)t).pool; - while (!blocker.isReleasable()) { - if (p.tryCompensate(p.ctl)) { - try { - do {} while (!blocker.isReleasable() && - !blocker.block()); - } finally { - p.incrementActiveCount(); - } - break; - } - } - } - else { - do {} while (!blocker.isReleasable() && - !blocker.block()); - } - } - - // AbstractExecutorService overrides. These rely on undocumented - // fact that ForkJoinTask.adapt returns ForkJoinTasks that also - // implement RunnableFuture. - - protected RunnableFuture newTaskFor(Runnable runnable, T value) { - return new ForkJoinTask.AdaptedRunnable(runnable, value); - } - - protected RunnableFuture newTaskFor(Callable callable) { - return new ForkJoinTask.AdaptedCallable(callable); - } - - // Unsafe mechanics - private static final sun.misc.Unsafe U; - private static final long CTL; - private static final long PARKBLOCKER; - private static final int ABASE; - private static final int ASHIFT; - private static final long STEALCOUNT; - private static final long PLOCK; - private static final long INDEXSEED; - private static final long QBASE; - private static final long QLOCK; - - static { - // initialize field offsets for CAS etc - try { - U = getUnsafe(); - Class k = ForkJoinPool.class; - CTL = U.objectFieldOffset - (k.getDeclaredField("ctl")); - STEALCOUNT = U.objectFieldOffset - (k.getDeclaredField("stealCount")); - PLOCK = U.objectFieldOffset - (k.getDeclaredField("plock")); - INDEXSEED = U.objectFieldOffset - (k.getDeclaredField("indexSeed")); - Class tk = Thread.class; - PARKBLOCKER = U.objectFieldOffset - (tk.getDeclaredField("parkBlocker")); - Class wk = WorkQueue.class; - QBASE = U.objectFieldOffset - (wk.getDeclaredField("base")); - QLOCK = U.objectFieldOffset - (wk.getDeclaredField("qlock")); - Class ak = ForkJoinTask[].class; - ABASE = U.arrayBaseOffset(ak); - int scale = U.arrayIndexScale(ak); - if ((scale & (scale - 1)) != 0) - throw new Error("data type scale not a power of two"); - ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); - } catch (Exception e) { - throw new Error(e); - } - - submitters = new ThreadLocal(); - defaultForkJoinWorkerThreadFactory = - new DefaultForkJoinWorkerThreadFactory(); - modifyThreadPermission = new RuntimePermission("modifyThread"); - - common = java.security.AccessController.doPrivileged - (new java.security.PrivilegedAction() { - public ForkJoinPool run() { return makeCommonPool(); }}); - int par = common.parallelism; // report 1 even if threads disabled - commonParallelism = par > 0 ? par : 1; - } - - /** - * Creates and returns the common pool, respecting user settings - * specified via system properties. - */ - private static ForkJoinPool makeCommonPool() { - int parallelism = -1; - ForkJoinWorkerThreadFactory factory - = defaultForkJoinWorkerThreadFactory; - UncaughtExceptionHandler handler = null; - try { // ignore exceptions in accessing/parsing properties - String pp = System.getProperty - ("java.util.concurrent.ForkJoinPool.common.parallelism"); - String fp = System.getProperty - ("java.util.concurrent.ForkJoinPool.common.threadFactory"); - String hp = System.getProperty - ("java.util.concurrent.ForkJoinPool.common.exceptionHandler"); - if (pp != null) - parallelism = Integer.parseInt(pp); - if (fp != null) - factory = ((ForkJoinWorkerThreadFactory)ClassLoader. - getSystemClassLoader().loadClass(fp).newInstance()); - if (hp != null) - handler = ((UncaughtExceptionHandler)ClassLoader. - getSystemClassLoader().loadClass(hp).newInstance()); - } catch (Exception ignore) { - } - - if (parallelism < 0 && // default 1 less than #cores - (parallelism = Runtime.getRuntime().availableProcessors() - 1) < 0) - parallelism = 0; - if (parallelism > MAX_CAP) - parallelism = MAX_CAP; - return new ForkJoinPool(parallelism, factory, handler, LIFO_QUEUE, - "ForkJoinPool.commonPool-worker-"); - } - - /** - * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. - * Replace with a simple call to Unsafe.getUnsafe when integrating - * into a jdk. - * - * @return a sun.misc.Unsafe - */ - private static sun.misc.Unsafe getUnsafe() { - try { - return sun.misc.Unsafe.getUnsafe(); - } catch (SecurityException tryReflectionInstead) {} - try { - return java.security.AccessController.doPrivileged - (new java.security.PrivilegedExceptionAction() { - public sun.misc.Unsafe run() throws Exception { - Class k = sun.misc.Unsafe.class; - for (java.lang.reflect.Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) - return k.cast(x); - } - throw new NoSuchFieldError("the Unsafe"); - }}); - } catch (java.security.PrivilegedActionException e) { - throw new RuntimeException("Could not initialize intrinsics", - e.getCause()); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/internal/chmv8/ForkJoinTask.java b/api/src/main/java/org/asynchttpclient/internal/chmv8/ForkJoinTask.java deleted file mode 100644 index 0eb8d96aef..0000000000 --- a/api/src/main/java/org/asynchttpclient/internal/chmv8/ForkJoinTask.java +++ /dev/null @@ -1,1560 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project 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. - */ - -/* - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -package org.asynchttpclient.internal.chmv8; - -import java.io.Serializable; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; -import java.lang.reflect.Constructor; -import java.util.Collection; -import java.util.List; -import java.util.RandomAccess; -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.Phaser; -import java.util.concurrent.RecursiveAction; -import java.util.concurrent.RecursiveTask; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.RunnableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.locks.ReentrantLock; - -/** - * Abstract base class for tasks that run within a {@link ForkJoinPool}. - * A {@code ForkJoinTask} is a thread-like entity that is much - * lighter weight than a normal thread. Huge numbers of tasks and - * subtasks may be hosted by a small number of actual threads in a - * ForkJoinPool, at the price of some usage limitations. - * - *

A "main" {@code ForkJoinTask} begins execution when it is - * explicitly submitted to a {@link ForkJoinPool}, or, if not already - * engaged in a ForkJoin computation, commenced in the {@link - * ForkJoinPool#commonPool()} via {@link #fork}, {@link #invoke}, or - * related methods. Once started, it will usually in turn start other - * subtasks. As indicated by the name of this class, many programs - * using {@code ForkJoinTask} employ only methods {@link #fork} and - * {@link #join}, or derivatives such as {@link - * #invokeAll(ForkJoinTask...) invokeAll}. However, this class also - * provides a number of other methods that can come into play in - * advanced usages, as well as extension mechanics that allow support - * of new forms of fork/join processing. - * - *

A {@code ForkJoinTask} is a lightweight form of {@link Future}. - * The efficiency of {@code ForkJoinTask}s stems from a set of - * restrictions (that are only partially statically enforceable) - * reflecting their main use as computational tasks calculating pure - * functions or operating on purely isolated objects. The primary - * coordination mechanisms are {@link #fork}, that arranges - * asynchronous execution, and {@link #join}, that doesn't proceed - * until the task's result has been computed. Computations should - * ideally avoid {@code synchronized} methods or blocks, and should - * minimize other blocking synchronization apart from joining other - * tasks or using synchronizers such as Phasers that are advertised to - * cooperate with fork/join scheduling. Subdividable tasks should also - * not perform blocking I/O, and should ideally access variables that - * are completely independent of those accessed by other running - * tasks. These guidelines are loosely enforced by not permitting - * checked exceptions such as {@code IOExceptions} to be - * thrown. However, computations may still encounter unchecked - * exceptions, that are rethrown to callers attempting to join - * them. These exceptions may additionally include {@link - * RejectedExecutionException} stemming from internal resource - * exhaustion, such as failure to allocate internal task - * queues. Rethrown exceptions behave in the same way as regular - * exceptions, but, when possible, contain stack traces (as displayed - * for example using {@code ex.printStackTrace()}) of both the thread - * that initiated the computation as well as the thread actually - * encountering the exception; minimally only the latter. - * - *

It is possible to define and use ForkJoinTasks that may block, - * but doing do requires three further considerations: (1) Completion - * of few if any other tasks should be dependent on a task - * that blocks on external synchronization or I/O. Event-style async - * tasks that are never joined (for example, those subclassing {@link - * CountedCompleter}) often fall into this category. (2) To minimize - * resource impact, tasks should be small; ideally performing only the - * (possibly) blocking action. (3) Unless the {@link - * ForkJoinPool.ManagedBlocker} API is used, or the number of possibly - * blocked tasks is known to be less than the pool's {@link - * ForkJoinPool#getParallelism} level, the pool cannot guarantee that - * enough threads will be available to ensure progress or good - * performance. - * - *

The primary method for awaiting completion and extracting - * results of a task is {@link #join}, but there are several variants: - * The {@link Future#get} methods support interruptible and/or timed - * waits for completion and report results using {@code Future} - * conventions. Method {@link #invoke} is semantically - * equivalent to {@code fork(); join()} but always attempts to begin - * execution in the current thread. The "quiet" forms of - * these methods do not extract results or report exceptions. These - * may be useful when a set of tasks are being executed, and you need - * to delay processing of results or exceptions until all complete. - * Method {@code invokeAll} (available in multiple versions) - * performs the most common form of parallel invocation: forking a set - * of tasks and joining them all. - * - *

In the most typical usages, a fork-join pair act like a call - * (fork) and return (join) from a parallel recursive function. As is - * the case with other forms of recursive calls, returns (joins) - * should be performed innermost-first. For example, {@code a.fork(); - * b.fork(); b.join(); a.join();} is likely to be substantially more - * efficient than joining {@code a} before {@code b}. - * - *

The execution status of tasks may be queried at several levels - * of detail: {@link #isDone} is true if a task completed in any way - * (including the case where a task was cancelled without executing); - * {@link #isCompletedNormally} is true if a task completed without - * cancellation or encountering an exception; {@link #isCancelled} is - * true if the task was cancelled (in which case {@link #getException} - * returns a {@link java.util.concurrent.CancellationException}); and - * {@link #isCompletedAbnormally} is true if a task was either - * cancelled or encountered an exception, in which case {@link - * #getException} will return either the encountered exception or - * {@link java.util.concurrent.CancellationException}. - * - *

The ForkJoinTask class is not usually directly subclassed. - * Instead, you subclass one of the abstract classes that support a - * particular style of fork/join processing, typically {@link - * RecursiveAction} for most computations that do not return results, - * {@link RecursiveTask} for those that do, and {@link - * CountedCompleter} for those in which completed actions trigger - * other actions. Normally, a concrete ForkJoinTask subclass declares - * fields comprising its parameters, established in a constructor, and - * then defines a {@code compute} method that somehow uses the control - * methods supplied by this base class. - * - *

Method {@link #join} and its variants are appropriate for use - * only when completion dependencies are acyclic; that is, the - * parallel computation can be described as a directed acyclic graph - * (DAG). Otherwise, executions may encounter a form of deadlock as - * tasks cyclically wait for each other. However, this framework - * supports other methods and techniques (for example the use of - * {@link Phaser}, {@link #helpQuiesce}, and {@link #complete}) that - * may be of use in constructing custom subclasses for problems that - * are not statically structured as DAGs. To support such usages, a - * ForkJoinTask may be atomically tagged with a {@code short} - * value using {@link #setForkJoinTaskTag} or {@link - * #compareAndSetForkJoinTaskTag} and checked using {@link - * #getForkJoinTaskTag}. The ForkJoinTask implementation does not use - * these {@code protected} methods or tags for any purpose, but they - * may be of use in the construction of specialized subclasses. For - * example, parallel graph traversals can use the supplied methods to - * avoid revisiting nodes/tasks that have already been processed. - * (Method names for tagging are bulky in part to encourage definition - * of methods that reflect their usage patterns.) - * - *

Most base support methods are {@code final}, to prevent - * overriding of implementations that are intrinsically tied to the - * underlying lightweight task scheduling framework. Developers - * creating new basic styles of fork/join processing should minimally - * implement {@code protected} methods {@link #exec}, {@link - * #setRawResult}, and {@link #getRawResult}, while also introducing - * an abstract computational method that can be implemented in its - * subclasses, possibly relying on other {@code protected} methods - * provided by this class. - * - *

ForkJoinTasks should perform relatively small amounts of - * computation. Large tasks should be split into smaller subtasks, - * usually via recursive decomposition. As a very rough rule of thumb, - * a task should perform more than 100 and less than 10000 basic - * computational steps, and should avoid indefinite looping. If tasks - * are too big, then parallelism cannot improve throughput. If too - * small, then memory and internal task maintenance overhead may - * overwhelm processing. - * - *

This class provides {@code adapt} methods for {@link Runnable} - * and {@link Callable}, that may be of use when mixing execution of - * {@code ForkJoinTasks} with other kinds of tasks. When all tasks are - * of this form, consider using a pool constructed in asyncMode. - * - *

ForkJoinTasks are {@code Serializable}, which enables them to be - * used in extensions such as remote execution frameworks. It is - * sensible to serialize tasks only before or after, but not during, - * execution. Serialization is not relied on during execution itself. - * - * @since 1.7 - * @author Doug Lea - */ -@SuppressWarnings("all") -public abstract class ForkJoinTask implements Future, Serializable { - - /* - * See the internal documentation of class ForkJoinPool for a - * general implementation overview. ForkJoinTasks are mainly - * responsible for maintaining their "status" field amidst relays - * to methods in ForkJoinWorkerThread and ForkJoinPool. - * - * The methods of this class are more-or-less layered into - * (1) basic status maintenance - * (2) execution and awaiting completion - * (3) user-level methods that additionally report results. - * This is sometimes hard to see because this file orders exported - * methods in a way that flows well in javadocs. - */ - - /* - * The status field holds run control status bits packed into a - * single int to minimize footprint and to ensure atomicity (via - * CAS). Status is initially zero, and takes on nonnegative - * values until completed, upon which status (anded with - * DONE_MASK) holds value NORMAL, CANCELLED, or EXCEPTIONAL. Tasks - * undergoing blocking waits by other threads have the SIGNAL bit - * set. Completion of a stolen task with SIGNAL set awakens any - * waiters via notifyAll. Even though suboptimal for some - * purposes, we use basic builtin wait/notify to take advantage of - * "monitor inflation" in JVMs that we would otherwise need to - * emulate to avoid adding further per-task bookkeeping overhead. - * We want these monitors to be "fat", i.e., not use biasing or - * thin-lock techniques, so use some odd coding idioms that tend - * to avoid them, mainly by arranging that every synchronized - * block performs a wait, notifyAll or both. - * - * These control bits occupy only (some of) the upper half (16 - * bits) of status field. The lower bits are used for user-defined - * tags. - */ - - /** The run status of this task */ - volatile int status; // accessed directly by pool and workers - static final int DONE_MASK = 0xf0000000; // mask out non-completion bits - static final int NORMAL = 0xf0000000; // must be negative - static final int CANCELLED = 0xc0000000; // must be < NORMAL - static final int EXCEPTIONAL = 0x80000000; // must be < CANCELLED - static final int SIGNAL = 0x00010000; // must be >= 1 << 16 - static final int SMASK = 0x0000ffff; // short bits for tags - - /** - * Marks completion and wakes up threads waiting to join this - * task. - * - * @param completion one of NORMAL, CANCELLED, EXCEPTIONAL - * @return completion status on exit - */ - private int setCompletion(int completion) { - for (int s;;) { - if ((s = status) < 0) - return s; - if (U.compareAndSwapInt(this, STATUS, s, s | completion)) { - if ((s >>> 16) != 0) - synchronized (this) { notifyAll(); } - return completion; - } - } - } - - /** - * Primary execution method for stolen tasks. Unless done, calls - * exec and records status if completed, but doesn't wait for - * completion otherwise. - * - * @return status on exit from this method - */ - final int doExec() { - int s; boolean completed; - if ((s = status) >= 0) { - try { - completed = exec(); - } catch (Throwable rex) { - return setExceptionalCompletion(rex); - } - if (completed) - s = setCompletion(NORMAL); - } - return s; - } - - /** - * Tries to set SIGNAL status unless already completed. Used by - * ForkJoinPool. Other variants are directly incorporated into - * externalAwaitDone etc. - * - * @return true if successful - */ - final boolean trySetSignal() { - int s = status; - return s >= 0 && U.compareAndSwapInt(this, STATUS, s, s | SIGNAL); - } - - /** - * Blocks a non-worker-thread until completion. - * @return status upon completion - */ - private int externalAwaitDone() { - int s; - ForkJoinPool cp = ForkJoinPool.common; - if ((s = status) >= 0) { - if (cp != null) { - if (this instanceof CountedCompleter) - s = cp.externalHelpComplete((CountedCompleter)this); - else if (cp.tryExternalUnpush(this)) - s = doExec(); - } - if (s >= 0 && (s = status) >= 0) { - boolean interrupted = false; - do { - if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { - synchronized (this) { - if (status >= 0) { - try { - wait(); - } catch (InterruptedException ie) { - interrupted = true; - } - } - else - notifyAll(); - } - } - } while ((s = status) >= 0); - if (interrupted) - Thread.currentThread().interrupt(); - } - } - return s; - } - - /** - * Blocks a non-worker-thread until completion or interruption. - */ - private int externalInterruptibleAwaitDone() throws InterruptedException { - int s; - ForkJoinPool cp = ForkJoinPool.common; - if (Thread.interrupted()) - throw new InterruptedException(); - if ((s = status) >= 0 && cp != null) { - if (this instanceof CountedCompleter) - cp.externalHelpComplete((CountedCompleter)this); - else if (cp.tryExternalUnpush(this)) - doExec(); - } - while ((s = status) >= 0) { - if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { - synchronized (this) { - if (status >= 0) - wait(); - else - notifyAll(); - } - } - } - return s; - } - - - /** - * Implementation for join, get, quietlyJoin. Directly handles - * only cases of already-completed, external wait, and - * unfork+exec. Others are relayed to ForkJoinPool.awaitJoin. - * - * @return status upon completion - */ - private int doJoin() { - int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w; - return (s = status) < 0 ? s : - ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ? - (w = (wt = (ForkJoinWorkerThread)t).workQueue). - tryUnpush(this) && (s = doExec()) < 0 ? s : - wt.pool.awaitJoin(w, this) : - externalAwaitDone(); - } - - /** - * Implementation for invoke, quietlyInvoke. - * - * @return status upon completion - */ - private int doInvoke() { - int s; Thread t; ForkJoinWorkerThread wt; - return (s = doExec()) < 0 ? s : - ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ? - (wt = (ForkJoinWorkerThread)t).pool.awaitJoin(wt.workQueue, this) : - externalAwaitDone(); - } - - // Exception table support - - /** - * Table of exceptions thrown by tasks, to enable reporting by - * callers. Because exceptions are rare, we don't directly keep - * them with task objects, but instead use a weak ref table. Note - * that cancellation exceptions don't appear in the table, but are - * instead recorded as status values. - * - * Note: These statics are initialized below in static block. - */ - private static final ExceptionNode[] exceptionTable; - private static final ReentrantLock exceptionTableLock; - private static final ReferenceQueue exceptionTableRefQueue; - - /** - * Fixed capacity for exceptionTable. - */ - private static final int EXCEPTION_MAP_CAPACITY = 32; - - /** - * Key-value nodes for exception table. The chained hash table - * uses identity comparisons, full locking, and weak references - * for keys. The table has a fixed capacity because it only - * maintains task exceptions long enough for joiners to access - * them, so should never become very large for sustained - * periods. However, since we do not know when the last joiner - * completes, we must use weak references and expunge them. We do - * so on each operation (hence full locking). Also, some thread in - * any ForkJoinPool will call helpExpungeStaleExceptions when its - * pool becomes isQuiescent. - */ - static final class ExceptionNode extends WeakReference> { - final Throwable ex; - ExceptionNode next; - final long thrower; // use id not ref to avoid weak cycles - ExceptionNode(ForkJoinTask task, Throwable ex, ExceptionNode next) { - super(task, exceptionTableRefQueue); - this.ex = ex; - this.next = next; - this.thrower = Thread.currentThread().getId(); - } - } - - /** - * Records exception and sets status. - * - * @return status on exit - */ - final int recordExceptionalCompletion(Throwable ex) { - int s; - if ((s = status) >= 0) { - int h = System.identityHashCode(this); - final ReentrantLock lock = exceptionTableLock; - lock.lock(); - try { - expungeStaleExceptions(); - ExceptionNode[] t = exceptionTable; - int i = h & (t.length - 1); - for (ExceptionNode e = t[i]; ; e = e.next) { - if (e == null) { - t[i] = new ExceptionNode(this, ex, t[i]); - break; - } - if (e.get() == this) // already present - break; - } - } finally { - lock.unlock(); - } - s = setCompletion(EXCEPTIONAL); - } - return s; - } - - /** - * Records exception and possibly propagates. - * - * @return status on exit - */ - private int setExceptionalCompletion(Throwable ex) { - int s = recordExceptionalCompletion(ex); - if ((s & DONE_MASK) == EXCEPTIONAL) - internalPropagateException(ex); - return s; - } - - /** - * Hook for exception propagation support for tasks with completers. - */ - void internalPropagateException(Throwable ex) { - } - - /** - * Cancels, ignoring any exceptions thrown by cancel. Used during - * worker and pool shutdown. Cancel is spec'ed not to throw any - * exceptions, but if it does anyway, we have no recourse during - * shutdown, so guard against this case. - */ - static final void cancelIgnoringExceptions(ForkJoinTask t) { - if (t != null && t.status >= 0) { - try { - t.cancel(false); - } catch (Throwable ignore) { - } - } - } - - /** - * Removes exception node and clears status. - */ - private void clearExceptionalCompletion() { - int h = System.identityHashCode(this); - final ReentrantLock lock = exceptionTableLock; - lock.lock(); - try { - ExceptionNode[] t = exceptionTable; - int i = h & (t.length - 1); - ExceptionNode e = t[i]; - ExceptionNode pred = null; - while (e != null) { - ExceptionNode next = e.next; - if (e.get() == this) { - if (pred == null) - t[i] = next; - else - pred.next = next; - break; - } - pred = e; - e = next; - } - expungeStaleExceptions(); - status = 0; - } finally { - lock.unlock(); - } - } - - /** - * Returns a rethrowable exception for the given task, if - * available. To provide accurate stack traces, if the exception - * was not thrown by the current thread, we try to create a new - * exception of the same type as the one thrown, but with the - * recorded exception as its cause. If there is no such - * constructor, we instead try to use a no-arg constructor, - * followed by initCause, to the same effect. If none of these - * apply, or any fail due to other exceptions, we return the - * recorded exception, which is still correct, although it may - * contain a misleading stack trace. - * - * @return the exception, or null if none - */ - private Throwable getThrowableException() { - if ((status & DONE_MASK) != EXCEPTIONAL) - return null; - int h = System.identityHashCode(this); - ExceptionNode e; - final ReentrantLock lock = exceptionTableLock; - lock.lock(); - try { - expungeStaleExceptions(); - ExceptionNode[] t = exceptionTable; - e = t[h & (t.length - 1)]; - while (e != null && e.get() != this) - e = e.next; - } finally { - lock.unlock(); - } - Throwable ex; - if (e == null || (ex = e.ex) == null) - return null; - if (false && e.thrower != Thread.currentThread().getId()) { - Class ec = ex.getClass(); - try { - Constructor noArgCtor = null; - Constructor[] cs = ec.getConstructors();// public ctors only - for (int i = 0; i < cs.length; ++i) { - Constructor c = cs[i]; - Class[] ps = c.getParameterTypes(); - if (ps.length == 0) - noArgCtor = c; - else if (ps.length == 1 && ps[0] == Throwable.class) - return (Throwable)(c.newInstance(ex)); - } - if (noArgCtor != null) { - Throwable wx = (Throwable)(noArgCtor.newInstance()); - wx.initCause(ex); - return wx; - } - } catch (Exception ignore) { - } - } - return ex; - } - - /** - * Poll stale refs and remove them. Call only while holding lock. - */ - private static void expungeStaleExceptions() { - for (Object x; (x = exceptionTableRefQueue.poll()) != null;) { - if (x instanceof ExceptionNode) { - ForkJoinTask key = ((ExceptionNode)x).get(); - ExceptionNode[] t = exceptionTable; - int i = System.identityHashCode(key) & (t.length - 1); - ExceptionNode e = t[i]; - ExceptionNode pred = null; - while (e != null) { - ExceptionNode next = e.next; - if (e == x) { - if (pred == null) - t[i] = next; - else - pred.next = next; - break; - } - pred = e; - e = next; - } - } - } - } - - /** - * If lock is available, poll stale refs and remove them. - * Called from ForkJoinPool when pools become quiescent. - */ - static final void helpExpungeStaleExceptions() { - final ReentrantLock lock = exceptionTableLock; - if (lock.tryLock()) { - try { - expungeStaleExceptions(); - } finally { - lock.unlock(); - } - } - } - - /** - * A version of "sneaky throw" to relay exceptions - */ - static void rethrow(Throwable ex) { - if (ex != null) - ForkJoinTask.uncheckedThrow(ex); - } - - /** - * The sneaky part of sneaky throw, relying on generics - * limitations to evade compiler complaints about rethrowing - * unchecked exceptions - */ - @SuppressWarnings("unchecked") static - void uncheckedThrow(Throwable t) throws T { - throw (T)t; // rely on vacuous cast - } - - /** - * Throws exception, if any, associated with the given status. - */ - private void reportException(int s) { - if (s == CANCELLED) - throw new CancellationException(); - if (s == EXCEPTIONAL) - rethrow(getThrowableException()); - } - - // public methods - - /** - * Arranges to asynchronously execute this task in the pool the - * current task is running in, if applicable, or using the {@link - * ForkJoinPool#commonPool()} if not {@link #inForkJoinPool}. While - * it is not necessarily enforced, it is a usage error to fork a - * task more than once unless it has completed and been - * reinitialized. Subsequent modifications to the state of this - * task or any data it operates on are not necessarily - * consistently observable by any thread other than the one - * executing it unless preceded by a call to {@link #join} or - * related methods, or a call to {@link #isDone} returning {@code - * true}. - * - * @return {@code this}, to simplify usage - */ - public final ForkJoinTask fork() { - Thread t; - if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) - ((ForkJoinWorkerThread)t).workQueue.push(this); - else - ForkJoinPool.common.externalPush(this); - return this; - } - - /** - * Returns the result of the computation when it {@link #isDone is - * done}. This method differs from {@link #get()} in that - * abnormal completion results in {@code RuntimeException} or - * {@code Error}, not {@code ExecutionException}, and that - * interrupts of the calling thread do not cause the - * method to abruptly return by throwing {@code - * InterruptedException}. - * - * @return the computed result - */ - public final V join() { - int s; - if ((s = doJoin() & DONE_MASK) != NORMAL) - reportException(s); - return getRawResult(); - } - - /** - * Commences performing this task, awaits its completion if - * necessary, and returns its result, or throws an (unchecked) - * {@code RuntimeException} or {@code Error} if the underlying - * computation did so. - * - * @return the computed result - */ - public final V invoke() { - int s; - if ((s = doInvoke() & DONE_MASK) != NORMAL) - reportException(s); - return getRawResult(); - } - - /** - * Forks the given tasks, returning when {@code isDone} holds for - * each task or an (unchecked) exception is encountered, in which - * case the exception is rethrown. If more than one task - * encounters an exception, then this method throws any one of - * these exceptions. If any task encounters an exception, the - * other may be cancelled. However, the execution status of - * individual tasks is not guaranteed upon exceptional return. The - * status of each task may be obtained using {@link - * #getException()} and related methods to check if they have been - * cancelled, completed normally or exceptionally, or left - * unprocessed. - * - * @param t1 the first task - * @param t2 the second task - * @throws NullPointerException if any task is null - */ - public static void invokeAll(ForkJoinTask t1, ForkJoinTask t2) { - int s1, s2; - t2.fork(); - if ((s1 = t1.doInvoke() & DONE_MASK) != NORMAL) - t1.reportException(s1); - if ((s2 = t2.doJoin() & DONE_MASK) != NORMAL) - t2.reportException(s2); - } - - /** - * Forks the given tasks, returning when {@code isDone} holds for - * each task or an (unchecked) exception is encountered, in which - * case the exception is rethrown. If more than one task - * encounters an exception, then this method throws any one of - * these exceptions. If any task encounters an exception, others - * may be cancelled. However, the execution status of individual - * tasks is not guaranteed upon exceptional return. The status of - * each task may be obtained using {@link #getException()} and - * related methods to check if they have been cancelled, completed - * normally or exceptionally, or left unprocessed. - * - * @param tasks the tasks - * @throws NullPointerException if any task is null - */ - public static void invokeAll(ForkJoinTask... tasks) { - Throwable ex = null; - int last = tasks.length - 1; - for (int i = last; i >= 0; --i) { - ForkJoinTask t = tasks[i]; - if (t == null) { - if (ex == null) - ex = new NullPointerException(); - } - else if (i != 0) - t.fork(); - else if (t.doInvoke() < NORMAL && ex == null) - ex = t.getException(); - } - for (int i = 1; i <= last; ++i) { - ForkJoinTask t = tasks[i]; - if (t != null) { - if (ex != null) - t.cancel(false); - else if (t.doJoin() < NORMAL) - ex = t.getException(); - } - } - if (ex != null) - rethrow(ex); - } - - /** - * Forks all tasks in the specified collection, returning when - * {@code isDone} holds for each task or an (unchecked) exception - * is encountered, in which case the exception is rethrown. If - * more than one task encounters an exception, then this method - * throws any one of these exceptions. If any task encounters an - * exception, others may be cancelled. However, the execution - * status of individual tasks is not guaranteed upon exceptional - * return. The status of each task may be obtained using {@link - * #getException()} and related methods to check if they have been - * cancelled, completed normally or exceptionally, or left - * unprocessed. - * - * @param tasks the collection of tasks - * @return the tasks argument, to simplify usage - * @throws NullPointerException if tasks or any element are null - */ - public static > Collection invokeAll(Collection tasks) { - if (!(tasks instanceof RandomAccess) || !(tasks instanceof List)) { - invokeAll(tasks.toArray(new ForkJoinTask[tasks.size()])); - return tasks; - } - @SuppressWarnings("unchecked") - List> ts = - (List>) tasks; - Throwable ex = null; - int last = ts.size() - 1; - for (int i = last; i >= 0; --i) { - ForkJoinTask t = ts.get(i); - if (t == null) { - if (ex == null) - ex = new NullPointerException(); - } - else if (i != 0) - t.fork(); - else if (t.doInvoke() < NORMAL && ex == null) - ex = t.getException(); - } - for (int i = 1; i <= last; ++i) { - ForkJoinTask t = ts.get(i); - if (t != null) { - if (ex != null) - t.cancel(false); - else if (t.doJoin() < NORMAL) - ex = t.getException(); - } - } - if (ex != null) - rethrow(ex); - return tasks; - } - - /** - * Attempts to cancel execution of this task. This attempt will - * fail if the task has already completed or could not be - * cancelled for some other reason. If successful, and this task - * has not started when {@code cancel} is called, execution of - * this task is suppressed. After this method returns - * successfully, unless there is an intervening call to {@link - * #reinitialize}, subsequent calls to {@link #isCancelled}, - * {@link #isDone}, and {@code cancel} will return {@code true} - * and calls to {@link #join} and related methods will result in - * {@code CancellationException}. - * - *

This method may be overridden in subclasses, but if so, must - * still ensure that these properties hold. In particular, the - * {@code cancel} method itself must not throw exceptions. - * - *

This method is designed to be invoked by other - * tasks. To terminate the current task, you can just return or - * throw an unchecked exception from its computation method, or - * invoke {@link #completeExceptionally(Throwable)}. - * - * @param mayInterruptIfRunning this value has no effect in the - * default implementation because interrupts are not used to - * control cancellation. - * - * @return {@code true} if this task is now cancelled - */ - public boolean cancel(boolean mayInterruptIfRunning) { - return (setCompletion(CANCELLED) & DONE_MASK) == CANCELLED; - } - - public final boolean isDone() { - return status < 0; - } - - public final boolean isCancelled() { - return (status & DONE_MASK) == CANCELLED; - } - - /** - * Returns {@code true} if this task threw an exception or was cancelled. - * - * @return {@code true} if this task threw an exception or was cancelled - */ - public final boolean isCompletedAbnormally() { - return status < NORMAL; - } - - /** - * Returns {@code true} if this task completed without throwing an - * exception and was not cancelled. - * - * @return {@code true} if this task completed without throwing an - * exception and was not cancelled - */ - public final boolean isCompletedNormally() { - return (status & DONE_MASK) == NORMAL; - } - - /** - * Returns the exception thrown by the base computation, or a - * {@code CancellationException} if cancelled, or {@code null} if - * none or if the method has not yet completed. - * - * @return the exception, or {@code null} if none - */ - public final Throwable getException() { - int s = status & DONE_MASK; - return ((s >= NORMAL) ? null : - (s == CANCELLED) ? new CancellationException() : - getThrowableException()); - } - - /** - * Completes this task abnormally, and if not already aborted or - * cancelled, causes it to throw the given exception upon - * {@code join} and related operations. This method may be used - * to induce exceptions in asynchronous tasks, or to force - * completion of tasks that would not otherwise complete. Its use - * in other situations is discouraged. This method is - * overridable, but overridden versions must invoke {@code super} - * implementation to maintain guarantees. - * - * @param ex the exception to throw. If this exception is not a - * {@code RuntimeException} or {@code Error}, the actual exception - * thrown will be a {@code RuntimeException} with cause {@code ex}. - */ - public void completeExceptionally(Throwable ex) { - setExceptionalCompletion((ex instanceof RuntimeException) || - (ex instanceof Error) ? ex : - new RuntimeException(ex)); - } - - /** - * Completes this task, and if not already aborted or cancelled, - * returning the given value as the result of subsequent - * invocations of {@code join} and related operations. This method - * may be used to provide results for asynchronous tasks, or to - * provide alternative handling for tasks that would not otherwise - * complete normally. Its use in other situations is - * discouraged. This method is overridable, but overridden - * versions must invoke {@code super} implementation to maintain - * guarantees. - * - * @param value the result value for this task - */ - public void complete(V value) { - try { - setRawResult(value); - } catch (Throwable rex) { - setExceptionalCompletion(rex); - return; - } - setCompletion(NORMAL); - } - - /** - * Completes this task normally without setting a value. The most - * recent value established by {@link #setRawResult} (or {@code - * null} by default) will be returned as the result of subsequent - * invocations of {@code join} and related operations. - * - * @since 1.8 - */ - public final void quietlyComplete() { - setCompletion(NORMAL); - } - - /** - * Waits if necessary for the computation to complete, and then - * retrieves its result. - * - * @return the computed result - * @throws CancellationException if the computation was cancelled - * @throws ExecutionException if the computation threw an - * exception - * @throws InterruptedException if the current thread is not a - * member of a ForkJoinPool and was interrupted while waiting - */ - public final V get() throws InterruptedException, ExecutionException { - int s = (Thread.currentThread() instanceof ForkJoinWorkerThread) ? - doJoin() : externalInterruptibleAwaitDone(); - Throwable ex; - if ((s &= DONE_MASK) == CANCELLED) - throw new CancellationException(); - if (s == EXCEPTIONAL && (ex = getThrowableException()) != null) - throw new ExecutionException(ex); - return getRawResult(); - } - - /** - * Waits if necessary for at most the given time for the computation - * to complete, and then retrieves its result, if available. - * - * @param timeout the maximum time to wait - * @param unit the time unit of the timeout argument - * @return the computed result - * @throws CancellationException if the computation was cancelled - * @throws ExecutionException if the computation threw an - * exception - * @throws InterruptedException if the current thread is not a - * member of a ForkJoinPool and was interrupted while waiting - * @throws TimeoutException if the wait timed out - */ - public final V get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - if (Thread.interrupted()) - throw new InterruptedException(); - // Messy in part because we measure in nanosecs, but wait in millisecs - int s; long ms; - long ns = unit.toNanos(timeout); - ForkJoinPool cp; - if ((s = status) >= 0 && ns > 0L) { - long deadline = System.nanoTime() + ns; - ForkJoinPool p = null; - ForkJoinPool.WorkQueue w = null; - Thread t = Thread.currentThread(); - if (t instanceof ForkJoinWorkerThread) { - ForkJoinWorkerThread wt = (ForkJoinWorkerThread)t; - p = wt.pool; - w = wt.workQueue; - p.helpJoinOnce(w, this); // no retries on failure - } - else if ((cp = ForkJoinPool.common) != null) { - if (this instanceof CountedCompleter) - cp.externalHelpComplete((CountedCompleter)this); - else if (cp.tryExternalUnpush(this)) - doExec(); - } - boolean canBlock = false; - boolean interrupted = false; - try { - while ((s = status) >= 0) { - if (w != null && w.qlock < 0) - cancelIgnoringExceptions(this); - else if (!canBlock) { - if (p == null || p.tryCompensate(p.ctl)) - canBlock = true; - } - else { - if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) > 0L && - U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { - synchronized (this) { - if (status >= 0) { - try { - wait(ms); - } catch (InterruptedException ie) { - if (p == null) - interrupted = true; - } - } - else - notifyAll(); - } - } - if ((s = status) < 0 || interrupted || - (ns = deadline - System.nanoTime()) <= 0L) - break; - } - } - } finally { - if (p != null && canBlock) - p.incrementActiveCount(); - } - if (interrupted) - throw new InterruptedException(); - } - if ((s &= DONE_MASK) != NORMAL) { - Throwable ex; - if (s == CANCELLED) - throw new CancellationException(); - if (s != EXCEPTIONAL) - throw new TimeoutException(); - if ((ex = getThrowableException()) != null) - throw new ExecutionException(ex); - } - return getRawResult(); - } - - /** - * Joins this task, without returning its result or throwing its - * exception. This method may be useful when processing - * collections of tasks when some have been cancelled or otherwise - * known to have aborted. - */ - public final void quietlyJoin() { - doJoin(); - } - - /** - * Commences performing this task and awaits its completion if - * necessary, without returning its result or throwing its - * exception. - */ - public final void quietlyInvoke() { - doInvoke(); - } - - /** - * Possibly executes tasks until the pool hosting the current task - * {@link ForkJoinPool#isQuiescent is quiescent}. This method may - * be of use in designs in which many tasks are forked, but none - * are explicitly joined, instead executing them until all are - * processed. - */ - public static void helpQuiesce() { - Thread t; - if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) { - ForkJoinWorkerThread wt = (ForkJoinWorkerThread)t; - wt.pool.helpQuiescePool(wt.workQueue); - } - else - ForkJoinPool.quiesceCommonPool(); - } - - /** - * Resets the internal bookkeeping state of this task, allowing a - * subsequent {@code fork}. This method allows repeated reuse of - * this task, but only if reuse occurs when this task has either - * never been forked, or has been forked, then completed and all - * outstanding joins of this task have also completed. Effects - * under any other usage conditions are not guaranteed. - * This method may be useful when executing - * pre-constructed trees of subtasks in loops. - * - *

Upon completion of this method, {@code isDone()} reports - * {@code false}, and {@code getException()} reports {@code - * null}. However, the value returned by {@code getRawResult} is - * unaffected. To clear this value, you can invoke {@code - * setRawResult(null)}. - */ - public void reinitialize() { - if ((status & DONE_MASK) == EXCEPTIONAL) - clearExceptionalCompletion(); - else - status = 0; - } - - /** - * Returns the pool hosting the current task execution, or null - * if this task is executing outside of any ForkJoinPool. - * - * @see #inForkJoinPool - * @return the pool, or {@code null} if none - */ - public static ForkJoinPool getPool() { - Thread t = Thread.currentThread(); - return (t instanceof ForkJoinWorkerThread) ? - ((ForkJoinWorkerThread) t).pool : null; - } - - /** - * Returns {@code true} if the current thread is a {@link - * ForkJoinWorkerThread} executing as a ForkJoinPool computation. - * - * @return {@code true} if the current thread is a {@link - * ForkJoinWorkerThread} executing as a ForkJoinPool computation, - * or {@code false} otherwise - */ - public static boolean inForkJoinPool() { - return Thread.currentThread() instanceof ForkJoinWorkerThread; - } - - /** - * Tries to unschedule this task for execution. This method will - * typically (but is not guaranteed to) succeed if this task is - * the most recently forked task by the current thread, and has - * not commenced executing in another thread. This method may be - * useful when arranging alternative local processing of tasks - * that could have been, but were not, stolen. - * - * @return {@code true} if unforked - */ - public boolean tryUnfork() { - Thread t; - return (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ? - ((ForkJoinWorkerThread)t).workQueue.tryUnpush(this) : - ForkJoinPool.common.tryExternalUnpush(this)); - } - - /** - * Returns an estimate of the number of tasks that have been - * forked by the current worker thread but not yet executed. This - * value may be useful for heuristic decisions about whether to - * fork other tasks. - * - * @return the number of tasks - */ - public static int getQueuedTaskCount() { - Thread t; ForkJoinPool.WorkQueue q; - if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) - q = ((ForkJoinWorkerThread)t).workQueue; - else - q = ForkJoinPool.commonSubmitterQueue(); - return (q == null) ? 0 : q.queueSize(); - } - - /** - * Returns an estimate of how many more locally queued tasks are - * held by the current worker thread than there are other worker - * threads that might steal them, or zero if this thread is not - * operating in a ForkJoinPool. This value may be useful for - * heuristic decisions about whether to fork other tasks. In many - * usages of ForkJoinTasks, at steady state, each worker should - * aim to maintain a small constant surplus (for example, 3) of - * tasks, and to process computations locally if this threshold is - * exceeded. - * - * @return the surplus number of tasks, which may be negative - */ - public static int getSurplusQueuedTaskCount() { - return ForkJoinPool.getSurplusQueuedTaskCount(); - } - - // Extension methods - - /** - * Returns the result that would be returned by {@link #join}, even - * if this task completed abnormally, or {@code null} if this task - * is not known to have been completed. This method is designed - * to aid debugging, as well as to support extensions. Its use in - * any other context is discouraged. - * - * @return the result, or {@code null} if not completed - */ - public abstract V getRawResult(); - - /** - * Forces the given value to be returned as a result. This method - * is designed to support extensions, and should not in general be - * called otherwise. - * - * @param value the value - */ - protected abstract void setRawResult(V value); - - /** - * Immediately performs the base action of this task and returns - * true if, upon return from this method, this task is guaranteed - * to have completed normally. This method may return false - * otherwise, to indicate that this task is not necessarily - * complete (or is not known to be complete), for example in - * asynchronous actions that require explicit invocations of - * completion methods. This method may also throw an (unchecked) - * exception to indicate abnormal exit. This method is designed to - * support extensions, and should not in general be called - * otherwise. - * - * @return {@code true} if this task is known to have completed normally - */ - protected abstract boolean exec(); - - /** - * Returns, but does not unschedule or execute, a task queued by - * the current thread but not yet executed, if one is immediately - * available. There is no guarantee that this task will actually - * be polled or executed next. Conversely, this method may return - * null even if a task exists but cannot be accessed without - * contention with other threads. This method is designed - * primarily to support extensions, and is unlikely to be useful - * otherwise. - * - * @return the next task, or {@code null} if none are available - */ - protected static ForkJoinTask peekNextLocalTask() { - Thread t; ForkJoinPool.WorkQueue q; - if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) - q = ((ForkJoinWorkerThread)t).workQueue; - else - q = ForkJoinPool.commonSubmitterQueue(); - return (q == null) ? null : q.peek(); - } - - /** - * Unschedules and returns, without executing, the next task - * queued by the current thread but not yet executed, if the - * current thread is operating in a ForkJoinPool. This method is - * designed primarily to support extensions, and is unlikely to be - * useful otherwise. - * - * @return the next task, or {@code null} if none are available - */ - protected static ForkJoinTask pollNextLocalTask() { - Thread t; - return ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ? - ((ForkJoinWorkerThread)t).workQueue.nextLocalTask() : - null; - } - - /** - * If the current thread is operating in a ForkJoinPool, - * unschedules and returns, without executing, the next task - * queued by the current thread but not yet executed, if one is - * available, or if not available, a task that was forked by some - * other thread, if available. Availability may be transient, so a - * {@code null} result does not necessarily imply quiescence of - * the pool this task is operating in. This method is designed - * primarily to support extensions, and is unlikely to be useful - * otherwise. - * - * @return a task, or {@code null} if none are available - */ - protected static ForkJoinTask pollTask() { - Thread t; ForkJoinWorkerThread wt; - return ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ? - (wt = (ForkJoinWorkerThread)t).pool.nextTaskFor(wt.workQueue) : - null; - } - - // tag operations - - /** - * Returns the tag for this task. - * - * @return the tag for this task - * @since 1.8 - */ - public final short getForkJoinTaskTag() { - return (short)status; - } - - /** - * Atomically sets the tag value for this task. - * - * @param tag the tag value - * @return the previous value of the tag - * @since 1.8 - */ - public final short setForkJoinTaskTag(short tag) { - for (int s;;) { - if (U.compareAndSwapInt(this, STATUS, s = status, - (s & ~SMASK) | (tag & SMASK))) - return (short)s; - } - } - - /** - * Atomically conditionally sets the tag value for this task. - * Among other applications, tags can be used as visit markers - * in tasks operating on graphs, as in methods that check: {@code - * if (task.compareAndSetForkJoinTaskTag((short)0, (short)1))} - * before processing, otherwise exiting because the node has - * already been visited. - * - * @param e the expected tag value - * @param tag the new tag value - * @return {@code true} if successful; i.e., the current value was - * equal to e and is now tag. - * @since 1.8 - */ - public final boolean compareAndSetForkJoinTaskTag(short e, short tag) { - for (int s;;) { - if ((short)(s = status) != e) - return false; - if (U.compareAndSwapInt(this, STATUS, s, - (s & ~SMASK) | (tag & SMASK))) - return true; - } - } - - /** - * Adaptor for Runnables. This implements RunnableFuture - * to be compliant with AbstractExecutorService constraints - * when used in ForkJoinPool. - */ - static final class AdaptedRunnable extends ForkJoinTask - implements RunnableFuture { - final Runnable runnable; - T result; - AdaptedRunnable(Runnable runnable, T result) { - if (runnable == null) throw new NullPointerException(); - this.runnable = runnable; - this.result = result; // OK to set this even before completion - } - public final T getRawResult() { return result; } - public final void setRawResult(T v) { result = v; } - public final boolean exec() { runnable.run(); return true; } - public final void run() { invoke(); } - private static final long serialVersionUID = 5232453952276885070L; - } - - /** - * Adaptor for Runnables without results - */ - static final class AdaptedRunnableAction extends ForkJoinTask - implements RunnableFuture { - final Runnable runnable; - AdaptedRunnableAction(Runnable runnable) { - if (runnable == null) throw new NullPointerException(); - this.runnable = runnable; - } - public final Void getRawResult() { return null; } - public final void setRawResult(Void v) { } - public final boolean exec() { runnable.run(); return true; } - public final void run() { invoke(); } - private static final long serialVersionUID = 5232453952276885070L; - } - - /** - * Adaptor for Runnables in which failure forces worker exception - */ - static final class RunnableExecuteAction extends ForkJoinTask { - final Runnable runnable; - RunnableExecuteAction(Runnable runnable) { - if (runnable == null) throw new NullPointerException(); - this.runnable = runnable; - } - public final Void getRawResult() { return null; } - public final void setRawResult(Void v) { } - public final boolean exec() { runnable.run(); return true; } - void internalPropagateException(Throwable ex) { - rethrow(ex); // rethrow outside exec() catches. - } - private static final long serialVersionUID = 5232453952276885070L; - } - - /** - * Adaptor for Callables - */ - static final class AdaptedCallable extends ForkJoinTask - implements RunnableFuture { - final Callable callable; - T result; - AdaptedCallable(Callable callable) { - if (callable == null) throw new NullPointerException(); - this.callable = callable; - } - public final T getRawResult() { return result; } - public final void setRawResult(T v) { result = v; } - public final boolean exec() { - try { - result = callable.call(); - return true; - } catch (Error err) { - throw err; - } catch (RuntimeException rex) { - throw rex; - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - public final void run() { invoke(); } - private static final long serialVersionUID = 2838392045355241008L; - } - - /** - * Returns a new {@code ForkJoinTask} that performs the {@code run} - * method of the given {@code Runnable} as its action, and returns - * a null result upon {@link #join}. - * - * @param runnable the runnable action - * @return the task - */ - public static ForkJoinTask adapt(Runnable runnable) { - return new AdaptedRunnableAction(runnable); - } - - /** - * Returns a new {@code ForkJoinTask} that performs the {@code run} - * method of the given {@code Runnable} as its action, and returns - * the given result upon {@link #join}. - * - * @param runnable the runnable action - * @param result the result upon completion - * @return the task - */ - public static ForkJoinTask adapt(Runnable runnable, T result) { - return new AdaptedRunnable(runnable, result); - } - - /** - * Returns a new {@code ForkJoinTask} that performs the {@code call} - * method of the given {@code Callable} as its action, and returns - * its result upon {@link #join}, translating any checked exceptions - * encountered into {@code RuntimeException}. - * - * @param callable the callable action - * @return the task - */ - public static ForkJoinTask adapt(Callable callable) { - return new AdaptedCallable(callable); - } - - // Serialization support - - private static final long serialVersionUID = -7721805057305804111L; - - /** - * Saves this task to a stream (that is, serializes it). - * - * @serialData the current run status and the exception thrown - * during execution, or {@code null} if none - */ - private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException { - s.defaultWriteObject(); - s.writeObject(getException()); - } - - /** - * Reconstitutes this task from a stream (that is, deserializes it). - */ - private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - s.defaultReadObject(); - Object ex = s.readObject(); - if (ex != null) - setExceptionalCompletion((Throwable)ex); - } - - // Unsafe mechanics - private static final sun.misc.Unsafe U; - private static final long STATUS; - - static { - exceptionTableLock = new ReentrantLock(); - exceptionTableRefQueue = new ReferenceQueue(); - exceptionTable = new ExceptionNode[EXCEPTION_MAP_CAPACITY]; - try { - U = getUnsafe(); - Class k = ForkJoinTask.class; - STATUS = U.objectFieldOffset - (k.getDeclaredField("status")); - } catch (Exception e) { - throw new Error(e); - } - } - - /** - * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. - * Replace with a simple call to Unsafe.getUnsafe when integrating - * into a jdk. - * - * @return a sun.misc.Unsafe - */ - private static sun.misc.Unsafe getUnsafe() { - try { - return sun.misc.Unsafe.getUnsafe(); - } catch (SecurityException tryReflectionInstead) {} - try { - return java.security.AccessController.doPrivileged - (new java.security.PrivilegedExceptionAction() { - public sun.misc.Unsafe run() throws Exception { - Class k = sun.misc.Unsafe.class; - for (java.lang.reflect.Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) - return k.cast(x); - } - throw new NoSuchFieldError("the Unsafe"); - }}); - } catch (java.security.PrivilegedActionException e) { - throw new RuntimeException("Could not initialize intrinsics", - e.getCause()); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/internal/chmv8/ForkJoinWorkerThread.java b/api/src/main/java/org/asynchttpclient/internal/chmv8/ForkJoinWorkerThread.java deleted file mode 100644 index 27c8a1eda3..0000000000 --- a/api/src/main/java/org/asynchttpclient/internal/chmv8/ForkJoinWorkerThread.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project 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. - */ - -/* - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -package org.asynchttpclient.internal.chmv8; - - -/** - * A thread managed by a {@link ForkJoinPool}, which executes - * {@link ForkJoinTask}s. - * This class is subclassable solely for the sake of adding - * functionality -- there are no overridable methods dealing with - * scheduling or execution. However, you can override initialization - * and termination methods surrounding the main task processing loop. - * If you do create such a subclass, you will also need to supply a - * custom {@link ForkJoinPool.ForkJoinWorkerThreadFactory} to - * {@linkplain ForkJoinPool#ForkJoinPool use it} in a {@code ForkJoinPool}. - * - * @since 1.7 - * @author Doug Lea - */ -@SuppressWarnings("all") -public class ForkJoinWorkerThread extends Thread { - /* - * ForkJoinWorkerThreads are managed by ForkJoinPools and perform - * ForkJoinTasks. For explanation, see the internal documentation - * of class ForkJoinPool. - * - * This class just maintains links to its pool and WorkQueue. The - * pool field is set immediately upon construction, but the - * workQueue field is not set until a call to registerWorker - * completes. This leads to a visibility race, that is tolerated - * by requiring that the workQueue field is only accessed by the - * owning thread. - */ - - final ForkJoinPool pool; // the pool this thread works in - final ForkJoinPool.WorkQueue workQueue; // work-stealing mechanics - - /** - * Creates a ForkJoinWorkerThread operating in the given pool. - * - * @param pool the pool this thread works in - * @throws NullPointerException if pool is null - */ - protected ForkJoinWorkerThread(ForkJoinPool pool) { - // Use a placeholder until a useful name can be set in registerWorker - super("aForkJoinWorkerThread"); - this.pool = pool; - this.workQueue = pool.registerWorker(this); - } - - /** - * Returns the pool hosting this thread. - * - * @return the pool - */ - public ForkJoinPool getPool() { - return pool; - } - - /** - * Returns the unique index number of this thread in its pool. - * The returned value ranges from zero to the maximum number of - * threads (minus one) that may exist in the pool, and does not - * change during the lifetime of the thread. This method may be - * useful for applications that track status or collect results - * per-worker-thread rather than per-task. - * - * @return the index number - */ - public int getPoolIndex() { - return workQueue.poolIndex >>> 1; // ignore odd/even tag bit - } - - /** - * Initializes internal state after construction but before - * processing any tasks. If you override this method, you must - * invoke {@code super.onStart()} at the beginning of the method. - * Initialization requires care: Most fields must have legal - * default values, to ensure that attempted accesses from other - * threads work correctly even before this thread starts - * processing tasks. - */ - protected void onStart() { - } - - /** - * Performs cleanup associated with termination of this worker - * thread. If you override this method, you must invoke - * {@code super.onTermination} at the end of the overridden method. - * - * @param exception the exception causing this thread to abort due - * to an unrecoverable error, or {@code null} if completed normally - */ - protected void onTermination(Throwable exception) { - } - - /** - * This method is required to be public, but should never be - * called explicitly. It performs the main run loop to execute - * {@link ForkJoinTask}s. - */ - public void run() { - Throwable exception = null; - try { - onStart(); - pool.runWorker(workQueue); - } catch (Throwable ex) { - exception = ex; - } finally { - try { - onTermination(exception); - } catch (Throwable ex) { - if (exception == null) - exception = ex; - } finally { - pool.deregisterWorker(this, exception); - } - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/internal/chmv8/LongAdder.java b/api/src/main/java/org/asynchttpclient/internal/chmv8/LongAdder.java deleted file mode 100644 index 67bf25c2b3..0000000000 --- a/api/src/main/java/org/asynchttpclient/internal/chmv8/LongAdder.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project 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. - */ - -/* - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -package org.asynchttpclient.internal.chmv8; - -import java.io.Serializable; -import java.util.concurrent.atomic.AtomicLong; - -/** - * One or more variables that together maintain an initially zero - * {@code long} sum. When updates (method {@link #add}) are contended - * across threads, the set of variables may grow dynamically to reduce - * contention. Method {@link #sum} (or, equivalently, {@link - * #longValue}) returns the current total combined across the - * variables maintaining the sum. - * - *

This class is usually preferable to {@link AtomicLong} when - * multiple threads update a common sum that is used for purposes such - * as collecting statistics, not for fine-grained synchronization - * control. Under low update contention, the two classes have similar - * characteristics. But under high contention, expected throughput of - * this class is significantly higher, at the expense of higher space - * consumption. - * - *

This class extends {@link Number}, but does not define - * methods such as {@code equals}, {@code hashCode} and {@code - * compareTo} because instances are expected to be mutated, and so are - * not useful as collection keys. - * - *

jsr166e note: This class is targeted to be placed in - * java.util.concurrent.atomic. - * - * @since 1.8 - * @author Doug Lea - */ -@SuppressWarnings("all") -public class LongAdder extends Striped64 implements Serializable { - private static final long serialVersionUID = 7249069246863182397L; - - /** - * Version of plus for use in retryUpdate - */ - final long fn(long v, long x) { return v + x; } - - /** - * Creates a new adder with initial sum of zero. - */ - public LongAdder() { - } - - /** - * Adds the given value. - * - * @param x the value to add - */ - public void add(long x) { - Cell[] as; long b, v; HashCode hc; Cell a; int n; - if ((as = cells) != null || !casBase(b = base, b + x)) { - boolean uncontended = true; - int h = (hc = threadHashCode.get()).code; - if (as == null || (n = as.length) < 1 || - (a = as[(n - 1) & h]) == null || - !(uncontended = a.cas(v = a.value, v + x))) - retryUpdate(x, hc, uncontended); - } - } - - /** - * Equivalent to {@code add(1)}. - */ - public void increment() { - add(1L); - } - - /** - * Equivalent to {@code add(-1)}. - */ - public void decrement() { - add(-1L); - } - - /** - * Returns the current sum. The returned value is NOT an - * atomic snapshot; invocation in the absence of concurrent - * updates returns an accurate result, but concurrent updates that - * occur while the sum is being calculated might not be - * incorporated. - * - * @return the sum - */ - public long sum() { - long sum = base; - Cell[] as = cells; - if (as != null) { - int n = as.length; - for (int i = 0; i < n; ++i) { - Cell a = as[i]; - if (a != null) - sum += a.value; - } - } - return sum; - } - - /** - * Resets variables maintaining the sum to zero. This method may - * be a useful alternative to creating a new adder, but is only - * effective if there are no concurrent updates. Because this - * method is intrinsically racy, it should only be used when it is - * known that no threads are concurrently updating. - */ - public void reset() { - internalReset(0L); - } - - /** - * Equivalent in effect to {@link #sum} followed by {@link - * #reset}. This method may apply for example during quiescent - * points between multithreaded computations. If there are - * updates concurrent with this method, the returned value is - * not guaranteed to be the final value occurring before - * the reset. - * - * @return the sum - */ - public long sumThenReset() { - long sum = base; - Cell[] as = cells; - base = 0L; - if (as != null) { - int n = as.length; - for (int i = 0; i < n; ++i) { - Cell a = as[i]; - if (a != null) { - sum += a.value; - a.value = 0L; - } - } - } - return sum; - } - - /** - * Returns the String representation of the {@link #sum}. - * @return the String representation of the {@link #sum} - */ - public String toString() { - return Long.toString(sum()); - } - - /** - * Equivalent to {@link #sum}. - * - * @return the sum - */ - public long longValue() { - return sum(); - } - - /** - * Returns the {@link #sum} as an {@code int} after a narrowing - * primitive conversion. - */ - public int intValue() { - return (int)sum(); - } - - /** - * Returns the {@link #sum} as a {@code float} - * after a widening primitive conversion. - */ - public float floatValue() { - return (float)sum(); - } - - /** - * Returns the {@link #sum} as a {@code double} after a widening - * primitive conversion. - */ - public double doubleValue() { - return (double)sum(); - } - - private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException { - s.defaultWriteObject(); - s.writeLong(sum()); - } - - private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - s.defaultReadObject(); - busy = 0; - cells = null; - base = s.readLong(); - } - -} diff --git a/api/src/main/java/org/asynchttpclient/internal/chmv8/Striped64.java b/api/src/main/java/org/asynchttpclient/internal/chmv8/Striped64.java deleted file mode 100644 index 84e99d5410..0000000000 --- a/api/src/main/java/org/asynchttpclient/internal/chmv8/Striped64.java +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright 2013 The Netty Project - * - * The Netty Project 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. - */ - -/* - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -package org.asynchttpclient.internal.chmv8; - -import java.util.Random; - -/** - * A package-local class holding common representation and mechanics - * for classes supporting dynamic striping on 64bit values. The class - * extends Number so that concrete subclasses must publicly do so. - */ -@SuppressWarnings("all") -abstract class Striped64 extends Number { - /* - * This class maintains a lazily-initialized table of atomically - * updated variables, plus an extra "base" field. The table size - * is a power of two. Indexing uses masked per-thread hash codes. - * Nearly all declarations in this class are package-private, - * accessed directly by subclasses. - * - * Table entries are of class Cell; a variant of AtomicLong padded - * to reduce cache contention on most processors. Padding is - * overkill for most Atomics because they are usually irregularly - * scattered in memory and thus don't interfere much with each - * other. But Atomic objects residing in arrays will tend to be - * placed adjacent to each other, and so will most often share - * cache lines (with a huge negative performance impact) without - * this precaution. - * - * In part because Cells are relatively large, we avoid creating - * them until they are needed. When there is no contention, all - * updates are made to the base field. Upon first contention (a - * failed CAS on base update), the table is initialized to size 2. - * The table size is doubled upon further contention until - * reaching the nearest power of two greater than or equal to the - * number of CPUS. Table slots remain empty (null) until they are - * needed. - * - * A single spinlock ("busy") is used for initializing and - * resizing the table, as well as populating slots with new Cells. - * There is no need for a blocking lock; when the lock is not - * available, threads try other slots (or the base). During these - * retries, there is increased contention and reduced locality, - * which is still better than alternatives. - * - * Per-thread hash codes are initialized to random values. - * Contention and/or table collisions are indicated by failed - * CASes when performing an update operation (see method - * retryUpdate). Upon a collision, if the table size is less than - * the capacity, it is doubled in size unless some other thread - * holds the lock. If a hashed slot is empty, and lock is - * available, a new Cell is created. Otherwise, if the slot - * exists, a CAS is tried. Retries proceed by "double hashing", - * using a secondary hash (Marsaglia XorShift) to try to find a - * free slot. - * - * The table size is capped because, when there are more threads - * than CPUs, supposing that each thread were bound to a CPU, - * there would exist a perfect hash function mapping threads to - * slots that eliminates collisions. When we reach capacity, we - * search for this mapping by randomly varying the hash codes of - * colliding threads. Because search is random, and collisions - * only become known via CAS failures, convergence can be slow, - * and because threads are typically not bound to CPUS forever, - * may not occur at all. However, despite these limitations, - * observed contention rates are typically low in these cases. - * - * It is possible for a Cell to become unused when threads that - * once hashed to it terminate, as well as in the case where - * doubling the table causes no thread to hash to it under - * expanded mask. We do not try to detect or remove such cells, - * under the assumption that for long-running instances, observed - * contention levels will recur, so the cells will eventually be - * needed again; and for short-lived ones, it does not matter. - */ - - /** - * Padded variant of AtomicLong supporting only raw accesses plus CAS. - * The value field is placed between pads, hoping that the JVM doesn't - * reorder them. - * - * JVM intrinsics note: It would be possible to use a release-only - * form of CAS here, if it were provided. - */ - static final class Cell { - volatile long p0, p1, p2, p3, p4, p5, p6; - volatile long value; - volatile long q0, q1, q2, q3, q4, q5, q6; - Cell(long x) { value = x; } - - final boolean cas(long cmp, long val) { - return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); - } - - // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long valueOffset; - static { - try { - UNSAFE = getUnsafe(); - Class ak = Cell.class; - valueOffset = UNSAFE.objectFieldOffset - (ak.getDeclaredField("value")); - } catch (Exception e) { - throw new Error(e); - } - } - - } - - /** - * Holder for the thread-local hash code. The code is initially - * random, but may be set to a different value upon collisions. - */ - static final class HashCode { - static final Random rng = new Random(); - int code; - HashCode() { - int h = rng.nextInt(); // Avoid zero to allow xorShift rehash - code = (h == 0) ? 1 : h; - } - } - - /** - * The corresponding ThreadLocal class - */ - static final class ThreadHashCode extends ThreadLocal { - public HashCode initialValue() { return new HashCode(); } - } - - /** - * Static per-thread hash codes. Shared across all instances to - * reduce ThreadLocal pollution and because adjustments due to - * collisions in one table are likely to be appropriate for - * others. - */ - static final ThreadHashCode threadHashCode = new ThreadHashCode(); - - /** Number of CPUS, to place bound on table size */ - static final int NCPU = Runtime.getRuntime().availableProcessors(); - - /** - * Table of cells. When non-null, size is a power of 2. - */ - transient volatile Cell[] cells; - - /** - * Base value, used mainly when there is no contention, but also as - * a fallback during table initialization races. Updated via CAS. - */ - transient volatile long base; - - /** - * Spinlock (locked via CAS) used when resizing and/or creating Cells. - */ - transient volatile int busy; - - /** - * Package-private default constructor - */ - Striped64() { - } - - /** - * CASes the base field. - */ - final boolean casBase(long cmp, long val) { - return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val); - } - - /** - * CASes the busy field from 0 to 1 to acquire lock. - */ - final boolean casBusy() { - return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1); - } - - /** - * Computes the function of current and new value. Subclasses - * should open-code this update function for most uses, but the - * virtualized form is needed within retryUpdate. - * - * @param currentValue the current value (of either base or a cell) - * @param newValue the argument from a user update call - * @return result of the update function - */ - abstract long fn(long currentValue, long newValue); - - /** - * Handles cases of updates involving initialization, resizing, - * creating new Cells, and/or contention. See above for - * explanation. This method suffers the usual non-modularity - * problems of optimistic retry code, relying on rechecked sets of - * reads. - * - * @param x the value - * @param hc the hash code holder - * @param wasUncontended false if CAS failed before call - */ - final void retryUpdate(long x, HashCode hc, boolean wasUncontended) { - int h = hc.code; - boolean collide = false; // True if last slot nonempty - for (;;) { - Cell[] as; Cell a; int n; long v; - if ((as = cells) != null && (n = as.length) > 0) { - if ((a = as[(n - 1) & h]) == null) { - if (busy == 0) { // Try to attach new Cell - Cell r = new Cell(x); // Optimistically create - if (busy == 0 && casBusy()) { - boolean created = false; - try { // Recheck under lock - Cell[] rs; int m, j; - if ((rs = cells) != null && - (m = rs.length) > 0 && - rs[j = (m - 1) & h] == null) { - rs[j] = r; - created = true; - } - } finally { - busy = 0; - } - if (created) - break; - continue; // Slot is now non-empty - } - } - collide = false; - } - else if (!wasUncontended) // CAS already known to fail - wasUncontended = true; // Continue after rehash - else if (a.cas(v = a.value, fn(v, x))) - break; - else if (n >= NCPU || cells != as) - collide = false; // At max size or stale - else if (!collide) - collide = true; - else if (busy == 0 && casBusy()) { - try { - if (cells == as) { // Expand table unless stale - Cell[] rs = new Cell[n << 1]; - for (int i = 0; i < n; ++i) - rs[i] = as[i]; - cells = rs; - } - } finally { - busy = 0; - } - collide = false; - continue; // Retry with expanded table - } - h ^= h << 13; // Rehash - h ^= h >>> 17; - h ^= h << 5; - } - else if (busy == 0 && cells == as && casBusy()) { - boolean init = false; - try { // Initialize table - if (cells == as) { - Cell[] rs = new Cell[2]; - rs[h & 1] = new Cell(x); - cells = rs; - init = true; - } - } finally { - busy = 0; - } - if (init) - break; - } - else if (casBase(v = base, fn(v, x))) - break; // Fall back on using base - } - hc.code = h; // Record index for next time - } - - - /** - * Sets base and all cells to the given value. - */ - final void internalReset(long initialValue) { - Cell[] as = cells; - base = initialValue; - if (as != null) { - int n = as.length; - for (int i = 0; i < n; ++i) { - Cell a = as[i]; - if (a != null) - a.value = initialValue; - } - } - } - - // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long baseOffset; - private static final long busyOffset; - static { - try { - UNSAFE = getUnsafe(); - Class sk = Striped64.class; - baseOffset = UNSAFE.objectFieldOffset - (sk.getDeclaredField("base")); - busyOffset = UNSAFE.objectFieldOffset - (sk.getDeclaredField("busy")); - } catch (Exception e) { - throw new Error(e); - } - } - - /** - * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. - * Replace with a simple call to Unsafe.getUnsafe when integrating - * into a jdk. - * - * @return a sun.misc.Unsafe - */ - private static sun.misc.Unsafe getUnsafe() { - try { - return sun.misc.Unsafe.getUnsafe(); - } catch (SecurityException tryReflectionInstead) {} - try { - return java.security.AccessController.doPrivileged - (new java.security.PrivilegedExceptionAction() { - public sun.misc.Unsafe run() throws Exception { - Class k = sun.misc.Unsafe.class; - for (java.lang.reflect.Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) - return k.cast(x); - } - throw new NoSuchFieldError("the Unsafe"); - }}); - } catch (java.security.PrivilegedActionException e) { - throw new RuntimeException("Could not initialize intrinsics", - e.getCause()); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/netty/DiscardEvent.java b/api/src/main/java/org/asynchttpclient/netty/DiscardEvent.java deleted file mode 100644 index a66557ad29..0000000000 --- a/api/src/main/java/org/asynchttpclient/netty/DiscardEvent.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.netty; - -/** - * Simple marker for stopping publishing bytes - */ -public enum DiscardEvent { - INSTANCE -} diff --git a/api/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProvider.java b/api/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProvider.java deleted file mode 100644 index e91a1d17c1..0000000000 --- a/api/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProvider.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.asynchttpclient.netty; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.Request; - -public class NettyAsyncHttpProvider implements AsyncHttpProvider { - - public NettyAsyncHttpProvider(AsyncHttpClientConfig config) { - throw new UnsupportedOperationException("This implementation is just a stub"); - } - - @Override - public ListenableFuture execute(Request request, AsyncHandler handler) { - throw new UnsupportedOperationException("This implementation is just a stub"); - } - - @Override - public void close() { - throw new UnsupportedOperationException("This implementation is just a stub"); - } -} diff --git a/api/src/main/java/org/asynchttpclient/netty/channel/pool/ChannelPoolPartitionSelector.java b/api/src/main/java/org/asynchttpclient/netty/channel/pool/ChannelPoolPartitionSelector.java deleted file mode 100644 index ea39a8ef93..0000000000 --- a/api/src/main/java/org/asynchttpclient/netty/channel/pool/ChannelPoolPartitionSelector.java +++ /dev/null @@ -1,19 +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.netty.channel.pool; - -public interface ChannelPoolPartitionSelector { - - boolean select(Object partitionKey); -} diff --git a/api/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.java b/api/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.java deleted file mode 100755 index c02c638ae3..0000000000 --- a/api/src/main/java/org/asynchttpclient/netty/future/StackTraceInspector.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.netty.future; - -import java.io.IOException; -import java.nio.channels.ClosedChannelException; - -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 recoverOnConnectCloseException(Throwable t) { - return exceptionInMethod(t, "sun.nio.ch.SocketChannelImpl", "checkConnect") - || (t.getCause() != null && recoverOnConnectCloseException(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) { - - 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 (className.equals("sun.nio.ch.SocketDispatcher") && (methodName.equals("read") || methodName.equals("write"))) - return true; - } - } catch (Throwable ignore) { - } - - if (t.getCause() != null) - return recoverOnReadOrWriteException(t.getCause()); - - return false; - } -} diff --git a/api/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactoryBase.java b/api/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactoryBase.java deleted file mode 100644 index 15dfae536e..0000000000 --- a/api/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactoryBase.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (c) 2015 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.netty.request; - -import static org.asynchttpclient.ntlm.NtlmUtils.getNTLM; -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.HttpUtils.useProxyConnect; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import java.util.List; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Realm.AuthScheme; -import org.asynchttpclient.Request; -import org.asynchttpclient.ntlm.NtlmEngine; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.spnego.SpnegoEngine; -import org.asynchttpclient.uri.Uri; - -public abstract class NettyRequestFactoryBase { - - protected final AsyncHttpClientConfig config; - - public NettyRequestFactoryBase(AsyncHttpClientConfig config) { - this.config = config; - } - - protected abstract List getProxyAuthorizationHeader(Request request); - - protected String firstRequestOnlyProxyAuthorizationHeader(Request request, ProxyServer proxyServer, boolean connect) { - String proxyAuthorization = null; - - if (connect) { - List auth = getProxyAuthorizationHeader(request); - String ntlmHeader = getNTLM(auth); - if (ntlmHeader != null) { - proxyAuthorization = ntlmHeader; - } - - } else if (proxyServer != null && proxyServer.getPrincipal() != null && isNonEmpty(proxyServer.getNtlmDomain())) { - List auth = getProxyAuthorizationHeader(request); - if (getNTLM(auth) == null) { - String msg = NtlmEngine.INSTANCE.generateType1Msg(); - proxyAuthorization = "NTLM " + msg; - } - } - - return proxyAuthorization; - } - - protected String requestUri(Uri uri, ProxyServer proxyServer, boolean connect) { - if (connect) - return getAuthority(uri); - - else if (proxyServer != null && !useProxyConnect(uri)) - return uri.toUrl(); - - else { - String path = getNonEmptyPath(uri); - if (isNonEmpty(uri.getQuery())) - return path + "?" + uri.getQuery(); - else - return path; - } - } - - protected String systematicProxyAuthorizationHeader(Request request, ProxyServer proxyServer, Realm realm, boolean connect) { - - String proxyAuthorization = null; - - if (!connect && proxyServer != null && proxyServer.getPrincipal() != null && proxyServer.getScheme() == AuthScheme.BASIC) { - proxyAuthorization = computeBasicAuthentication(proxyServer); - } else if (realm != null && realm.getUsePreemptiveAuth() && realm.isTargetProxy()) { - - switch (realm.getScheme()) { - case BASIC: - proxyAuthorization = computeBasicAuthentication(realm); - break; - case DIGEST: - if (isNonEmpty(realm.getNonce())) - proxyAuthorization = computeDigestAuthentication(realm); - break; - case NTLM: - case KERBEROS: - case SPNEGO: - // NTLM, KERBEROS and SPNEGO are only set on the first request, - // see firstRequestOnlyAuthorizationHeader - case NONE: - break; - default: - throw new IllegalStateException("Invalid Authentication " + realm); - } - } - - return proxyAuthorization; - } - - protected String firstRequestOnlyAuthorizationHeader(Request request, ProxyServer proxyServer, Realm realm) { - String authorizationHeader = null; - - if (realm != null && realm.getUsePreemptiveAuth()) { - 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(); - - authorizationHeader = "Negotiate " + SpnegoEngine.instance().generateToken(host); - break; - default: - break; - } - } - - return authorizationHeader; - } - - protected String systematicAuthorizationHeader(Request request, Realm realm) { - - String authorizationHeader = null; - - if (realm != null && realm.getUsePreemptiveAuth()) { - - switch (realm.getScheme()) { - case BASIC: - authorizationHeader = computeBasicAuthentication(realm); - break; - case DIGEST: - if (isNonEmpty(realm.getNonce())) - authorizationHeader = computeDigestAuthentication(realm); - break; - case NTLM: - case KERBEROS: - case SPNEGO: - // NTLM, KERBEROS and SPNEGO are only set on the first request, - // see firstRequestOnlyAuthorizationHeader - case NONE: - break; - default: - throw new IllegalStateException("Invalid Authentication " + realm); - } - } - - return authorizationHeader; - } - - protected String connectionHeader(boolean allowConnectionPooling, boolean http11) { - if (allowConnectionPooling) - return "keep-alive"; - else if (http11) - return "close"; - else - return null; - } -} 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 167de12269..0000000000 --- a/api/src/main/java/org/asynchttpclient/ntlm/NtlmEngine.java +++ /dev/null @@ -1,1532 +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 - * . - * - */ -// fork from Apache HttpComponents -package org.asynchttpclient.ntlm; - -import static java.nio.charset.StandardCharsets.US_ASCII; - -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.nio.charset.UnsupportedCharsetException; -import java.security.Key; -import java.security.MessageDigest; -import java.util.Arrays; -import java.util.Locale; - -import javax.crypto.Cipher; -import javax.crypto.spec.SecretKeySpec; - -import org.asynchttpclient.util.Base64; - -/** - * Provides an implementation for NTLMv1, NTLMv2, and NTLM2 Session forms of the NTLM - * authentication protocol. - * - * @since 4.1 - */ -public final class NtlmEngine { - - public static final NtlmEngine INSTANCE = new NtlmEngine(); - - /** Unicode encoding */ - private static final Charset UNICODE_LITTLE_UNMARKED; - - static { - Charset c; - try { - c = Charset.forName("UnicodeLittleUnmarked"); - } catch (UnsupportedCharsetException e) { - c = null; - } - UNICODE_LITTLE_UNMARKED = c; - } - - 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 java.security.SecureRandom RND_GEN; - static { - java.security.SecureRandom rnd = null; - try { - rnd = java.security.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] = (byte) 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 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. - */ - private String getType3Message(final String user, final String password, final String host, final String domain, final byte[] nonce, - final int type2Flags, final String target, final byte[] targetInformation) throws NtlmEngineException { - 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) { - if (value == null) { - return null; - } - final int index = value.indexOf("."); - if (index != -1) { - return value.substring(0, index); - } - return value; - } - - /** Convert host to standard form */ - private static String convertHost(final String host) { - return stripDotSuffix(host); - } - - /** Convert domain to standard form */ - private static String convertDomain(final String domain) { - return stripDotSuffix(domain); - } - - private static int readULong(final byte[] src, final 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(final byte[] src, final 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(final byte[] src, final int index) throws NtlmEngineException { - 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() throws NtlmEngineException { - 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() throws NtlmEngineException { - 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 String target; - protected final byte[] targetInformation; - - // Information we can generate but may be passed in (for testing) - protected byte[] clientChallenge; - protected byte[] clientChallenge2; - protected byte[] secondaryKey; - protected byte[] timestamp; - - // Stuff we always generate - protected byte[] lmHash = null; - protected byte[] lmResponse = null; - protected byte[] ntlmHash = null; - protected byte[] ntlmResponse = null; - protected byte[] ntlmv2Hash = null; - protected byte[] lmv2Hash = null; - protected byte[] lmv2Response = null; - protected byte[] ntlmv2Blob = null; - protected byte[] ntlmv2Response = null; - protected byte[] ntlm2SessionResponse = null; - protected byte[] lm2SessionResponse = null; - protected byte[] lmUserSessionKey = null; - protected byte[] ntlmUserSessionKey = null; - protected byte[] ntlmv2UserSessionKey = null; - protected byte[] ntlm2SessionResponseUserSessionKey = null; - protected byte[] lanManagerSessionKey = null; - - public CipherGen(final String domain, final String user, final String password, final byte[] challenge, final String target, - final byte[] targetInformation, final byte[] clientChallenge, final byte[] clientChallenge2, final byte[] secondaryKey, - final byte[] 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; - } - - public CipherGen(final String domain, final String user, final String password, final byte[] challenge, final String target, - final byte[] targetInformation) { - this(domain, user, password, challenge, target, targetInformation, null, null, null, null); - } - - /** Calculate and return client challenge */ - public byte[] getClientChallenge() throws NtlmEngineException { - if (clientChallenge == null) { - clientChallenge = makeRandomChallenge(); - } - return clientChallenge; - } - - /** Calculate and return second client challenge */ - public byte[] getClientChallenge2() throws NtlmEngineException { - if (clientChallenge2 == null) { - clientChallenge2 = makeRandomChallenge(); - } - return clientChallenge2; - } - - /** Calculate and return random secondary key */ - public byte[] getSecondaryKey() throws NtlmEngineException { - if (secondaryKey == null) { - secondaryKey = makeSecondaryKey(); - } - return secondaryKey; - } - - /** Calculate and return the LMHash */ - public byte[] getLMHash() throws NtlmEngineException { - if (lmHash == null) { - lmHash = lmHash(password); - } - return lmHash; - } - - /** Calculate and return the LMResponse */ - public byte[] getLMResponse() throws NtlmEngineException { - if (lmResponse == null) { - lmResponse = lmResponse(getLMHash(), challenge); - } - return lmResponse; - } - - /** Calculate and return the NTLMHash */ - public byte[] getNTLMHash() throws NtlmEngineException { - if (ntlmHash == null) { - ntlmHash = ntlmHash(password); - } - return ntlmHash; - } - - /** Calculate and return the NTLMResponse */ - public byte[] getNTLMResponse() throws NtlmEngineException { - if (ntlmResponse == null) { - ntlmResponse = lmResponse(getNTLMHash(), challenge); - } - return ntlmResponse; - } - - /** Calculate the LMv2 hash */ - public byte[] getLMv2Hash() throws NtlmEngineException { - if (lmv2Hash == null) { - lmv2Hash = lmv2Hash(domain, user, getNTLMHash()); - } - return lmv2Hash; - } - - /** Calculate the NTLMv2 hash */ - public byte[] getNTLMv2Hash() throws NtlmEngineException { - 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 */ - public byte[] getNTLMv2Blob() throws NtlmEngineException { - if (ntlmv2Blob == null) { - ntlmv2Blob = createBlob(getClientChallenge2(), targetInformation, getTimestamp()); - } - return ntlmv2Blob; - } - - /** Calculate the NTLMv2Response */ - public byte[] getNTLMv2Response() throws NtlmEngineException { - if (ntlmv2Response == null) { - ntlmv2Response = lmv2Response(getNTLMv2Hash(), challenge, getNTLMv2Blob()); - } - return ntlmv2Response; - } - - /** Calculate the LMv2Response */ - public byte[] getLMv2Response() throws NtlmEngineException { - if (lmv2Response == null) { - lmv2Response = lmv2Response(getLMv2Hash(), challenge, getClientChallenge()); - } - return lmv2Response; - } - - /** Get NTLM2SessionResponse */ - public byte[] getNTLM2SessionResponse() throws NtlmEngineException { - if (ntlm2SessionResponse == null) { - ntlm2SessionResponse = ntlm2SessionResponse(getNTLMHash(), challenge, getClientChallenge()); - } - return ntlm2SessionResponse; - } - - /** Calculate and return LM2 session response */ - public byte[] getLM2SessionResponse() throws NtlmEngineException { - 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() throws NtlmEngineException { - 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() throws NtlmEngineException { - if (ntlmUserSessionKey == null) { - final MD4 md4 = new MD4(); - md4.update(getNTLMHash()); - ntlmUserSessionKey = md4.getOutput(); - } - return ntlmUserSessionKey; - } - - /** GetNTLMv2UserSessionKey */ - public byte[] getNTLMv2UserSessionKey() throws NtlmEngineException { - if (ntlmv2UserSessionKey == null) { - final byte[] ntlmv2hash = getNTLMv2Hash(); - final byte[] truncatedResponse = new byte[16]; - System.arraycopy(getNTLMv2Response(), 0, truncatedResponse, 0, 16); - ntlmv2UserSessionKey = hmacMD5(truncatedResponse, ntlmv2hash); - } - return ntlmv2UserSessionKey; - } - - /** Get NTLM2SessionResponseUserSessionKey */ - public byte[] getNTLM2SessionResponseUserSessionKey() throws NtlmEngineException { - 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() throws NtlmEngineException { - 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) throws NtlmEngineException { - final HMACMD5 hmacMD5 = new HMACMD5(key); - hmacMD5.update(value); - return hmacMD5.getOutput(); - } - - /** Calculates RC4 */ - private static byte[] RC4(final byte[] value, final byte[] key) throws NtlmEngineException { - 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) - throws NtlmEngineException { - try { - // 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]); - - 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) throws NtlmEngineException { - 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) throws NtlmEngineException { - if (UNICODE_LITTLE_UNMARKED == null) { - throw new NtlmEngineException("Unicode not supported"); - } - 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) throws NtlmEngineException { - if (UNICODE_LITTLE_UNMARKED == null) { - throw new NtlmEngineException("Unicode not supported"); - } - 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) throws NtlmEngineException { - if (UNICODE_LITTLE_UNMARKED == null) { - throw new NtlmEngineException("Unicode not supported"); - } - 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) throws NtlmEngineException { - 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) throws NtlmEngineException { - 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 = new byte[] { (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 }; - final byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; - final byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; - final byte[] unknown2 = new byte[] { (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); - 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] |= (byte) 0x01; - } else { - bytes[i] &= (byte) 0xfe; - } - } - } - - /** NTLM message generation, base class */ - private 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(final String messageBody, final 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 - final 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(final 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(final byte[] buffer, final 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(final int position) throws NtlmEngineException { - return NtlmEngine.readUShort(messageContents, position); - } - - /** Read a ulong from a position within the message buffer */ - protected int readULong(final int position) throws NtlmEngineException { - return NtlmEngine.readULong(messageContents, position); - } - - /** Read a security buffer from a position within the message buffer */ - protected byte[] readSecurityBuffer(final 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(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[] 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.encode(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 String target; - protected byte[] targetInfo; - protected int flags; - - Type2Message(final String message) throws NtlmEngineException { - 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 a 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: " + 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) { - 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 */ - 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; - protected byte[] 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 String target, final byte[] targetInformation) throws NtlmEngineException { - // 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(); - lmResp = gen.getLMv2Response(); - if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { - userSessionKey = gen.getLanManagerSessionKey(); - } else { - userSessionKey = gen.getNTLMv2UserSessionKey(); - } - } 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; - } - if (UNICODE_LITTLE_UNMARKED == null) { - throw new NtlmEngineException("Unicode not supported"); - } - 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 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(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; - curBufferPos += 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) throws NtlmEngineException { - 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] = (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() { - 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 user name, 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 String generateType3Msg(final String username, final String password, final String domain, final String workstation, - final String challenge) throws NtlmEngineException { - 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/api/src/main/java/org/asynchttpclient/ntlm/NtlmUtils.java b/api/src/main/java/org/asynchttpclient/ntlm/NtlmUtils.java deleted file mode 100644 index 797f5a6aae..0000000000 --- a/api/src/main/java/org/asynchttpclient/ntlm/NtlmUtils.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2015 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.ntlm; - -import java.util.List; - -public final class NtlmUtils { - - private NtlmUtils() { - } - - public static String getNTLM(List authenticateHeaders) { - if (authenticateHeaders != null) { - for (String authenticateHeader: authenticateHeaders) { - if (authenticateHeader.startsWith("NTLM")) - return authenticateHeader; - } - } - - return null; - } -} 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 c7835883c1..0000000000 --- a/api/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java +++ /dev/null @@ -1,316 +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 java.nio.charset.StandardCharsets.UTF_8; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; - -import org.asynchttpclient.Param; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilderBase; -import org.asynchttpclient.SignatureCalculator; -import org.asynchttpclient.uri.Uri; -import org.asynchttpclient.util.Base64; -import org.asynchttpclient.util.StringUtils; -import org.asynchttpclient.util.Utf8UrlEncoder; - -/** - * 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"; - - protected static final ThreadLocal NONCE_BUFFER = new ThreadLocal() { - protected byte[] initialValue() { - return 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; - } - - @Override - public void calculateAndAddSignature(Request request, RequestBuilderBase requestBuilder) { - String nonce = generateNonce(); - long timestamp = generateTimestamp(); - String signature = calculateSignature(request.getMethod(), request.getUri(), timestamp, nonce, request.getFormParams(), request.getQueryParams()); - String headerValue = constructAuthHeader(signature, nonce, timestamp); - requestBuilder.setHeader(HEADER_AUTHORIZATION, headerValue); - } - - private String baseUrl(Uri uri) { - /* 07-Oct-2010, tatu: URL may contain default port number; if so, need to extract - * from base URL. - */ - String scheme = uri.getScheme(); - - StringBuilder sb = StringUtils.stringBuilder(); - sb.append(scheme).append("://").append(uri.getHost()); - - int port = uri.getPort(); - if (scheme.equals("http")) { - if (port == 80) - port = -1; - } else if (scheme.equals("https")) { - if (port == 443) - port = -1; - } - - if (port != -1) - sb.append(':').append(port); - - if (isNonEmpty(uri.getPath())) - sb.append(uri.getPath()); - - return sb.toString(); - } - - private String encodedParams(long oauthTimestamp, String nonce, List formParams, List queryParams) { - /** - * List of all query and form parameters added to this request; needed - * for calculating request signature - */ - int allParametersSize = 5 - + (userAuth.getKey() != null ? 1 : 0) - + (formParams != null ? formParams.size() : 0) - + (queryParams != null ? queryParams.size() : 0); - OAuthParameterSet allParameters = new OAuthParameterSet(allParametersSize); - - // start with standard OAuth parameters we need - allParameters.add(KEY_OAUTH_CONSUMER_KEY, Utf8UrlEncoder.encodeQueryElement(consumerAuth.getKey())); - allParameters.add(KEY_OAUTH_NONCE, Utf8UrlEncoder.encodeQueryElement(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, Utf8UrlEncoder.encodeQueryElement(userAuth.getKey())); - } - allParameters.add(KEY_OAUTH_VERSION, OAUTH_VERSION_1_0); - - if (formParams != null) { - for (Param param : formParams) { - // formParams are not already encoded - allParameters.add(Utf8UrlEncoder.encodeQueryElement(param.getName()), Utf8UrlEncoder.encodeQueryElement(param.getValue())); - } - } - if (queryParams != null) { - for (Param param : queryParams) { - // queryParams are already encoded - allParameters.add(param.getName(), param.getValue()); - } - } - return allParameters.sortAndConcat(); - } - - StringBuilder signatureBaseString(String method, Uri uri, long oauthTimestamp, String nonce, - List formParams, List queryParams) { - - // beware: must generate first as we're using pooled StringBuilder - String baseUrl = baseUrl(uri); - String encodedParams = encodedParams(oauthTimestamp, nonce, formParams, queryParams); - - StringBuilder sb = StringUtils.stringBuilder(); - sb.append(method); // POST / GET etc (nothing to URL encode) - sb.append('&'); - Utf8UrlEncoder.encodeAndAppendQueryElement(sb, baseUrl); - - - // and all that needs to be URL encoded (... again!) - sb.append('&'); - Utf8UrlEncoder.encodeAndAppendQueryElement(sb, encodedParams); - return sb; - } - - /** - * Method for calculating OAuth signature using HMAC/SHA-1 method. - */ - public String calculateSignature(String method, Uri uri, long oauthTimestamp, String nonce, - List formParams, List queryParams) { - - StringBuilder sb = signatureBaseString(method, uri, oauthTimestamp, nonce, formParams, queryParams); - - ByteBuffer rawBase = StringUtils.charSequence2ByteBuffer(sb, UTF_8); - byte[] rawSignature = mac.digest(rawBase); - // and finally, base64 encoded... phew! - return Base64.encode(rawSignature); - } - - /** - * Method used for constructing - */ - private String constructAuthHeader(String signature, String nonce, long oauthTimestamp) { - StringBuilder sb = StringUtils.stringBuilder(); - 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.encodeAndAppendQueryElement(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.encodeAndAppendQueryElement(sb, nonce); - sb.append("\", "); - - sb.append(KEY_OAUTH_VERSION).append("=\"").append(OAUTH_VERSION_1_0).append("\""); - return sb.toString(); - } - - protected long generateTimestamp() { - return System.currentTimeMillis() / 1000L; - } - - protected String generateNonce() { - byte[] nonceBuffer = NONCE_BUFFER.get(); - ThreadLocalRandom.current().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 { - private final ArrayList allParameters; - - public OAuthParameterSet(int size) { - allParameters = new ArrayList<>(size); - } - - public OAuthParameterSet add(String key, String value) { - allParameters.add(new Parameter(key, value)); - 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 - 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 c699dd7d14..0000000000 --- a/api/src/main/java/org/asynchttpclient/oauth/ThreadSafeHMAC.java +++ /dev/null @@ -1,66 +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 java.nio.charset.StandardCharsets.UTF_8; - -import java.nio.ByteBuffer; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import org.asynchttpclient.util.StringUtils; -import org.asynchttpclient.util.Utf8UrlEncoder; - -/** - * 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) { - StringBuilder sb = StringUtils.stringBuilder(); - Utf8UrlEncoder.encodeAndAppendQueryElement(sb, consumerAuth.getSecret()); - sb.append('&'); - Utf8UrlEncoder.encodeAndAppendQueryElement(sb, userAuth.getSecret()); - byte[] keyBytes = StringUtils.charSequence2Bytes(sb, 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(ByteBuffer message) { - mac.reset(); - mac.update(message); - return mac.doFinal(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/proxy/ProxyServer.java b/api/src/main/java/org/asynchttpclient/proxy/ProxyServer.java deleted file mode 100644 index 3bf9845fed..0000000000 --- a/api/src/main/java/org/asynchttpclient/proxy/ProxyServer.java +++ /dev/null @@ -1,180 +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.proxy; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.asynchttpclient.Realm; -import org.asynchttpclient.Realm.AuthScheme; - -/** - * 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 Charset charset = UTF_8; - private String ntlmDomain = System.getProperty("http.auth.ntlm.domain", ""); - private String ntlmHost; - private AuthScheme scheme = AuthScheme.BASIC; - private boolean forceHttp10 = false; - - 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 Realm.RealmBuilder realmBuilder() { - return new Realm.RealmBuilder()// - .setNtlmDomain(ntlmDomain) - .setNtlmHost(ntlmHost) - .setPrincipal(principal) - .setPassword(password) - .setScheme(scheme); - } - - public Protocol getProtocol() { - return protocol; - } - - public String getHost() { - return host; - } - - public int getPort() { - return port; - } - - public String getPrincipal() { - return principal; - } - - public String getPassword() { - return password; - } - - public ProxyServer setCharset(Charset charset) { - this.charset = charset; - return this; - } - - 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; - } - - public AuthScheme getScheme() { - return scheme; - } - - public void setScheme(AuthScheme scheme) { - this.scheme = scheme; - } - - public String getNtlmHost() { - return ntlmHost; - } - - public void setNtlmHost(String ntlmHost) { - this.ntlmHost = ntlmHost; - } - - public boolean isForceHttp10() { - return forceHttp10; - } - - public void setForceHttp10(boolean forceHttp10) { - this.forceHttp10 = forceHttp10; - } - - @Override - public String toString() { - return url; - } -} diff --git a/api/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java b/api/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java deleted file mode 100644 index e7de49c6e9..0000000000 --- a/api/src/main/java/org/asynchttpclient/proxy/ProxyServerSelector.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.asynchttpclient.proxy; - -import org.asynchttpclient.uri.Uri; - -/** - * 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(Uri uri); - - /** - * A selector that always selects no proxy. - */ - static final ProxyServerSelector NO_PROXY_SELECTOR = new ProxyServerSelector() { - @Override - public ProxyServer select(Uri uri) { - return null; - } - }; -} diff --git a/api/src/main/java/org/asynchttpclient/request/body/Body.java b/api/src/main/java/org/asynchttpclient/request/body/Body.java deleted file mode 100644 index 5b0f64867b..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/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.request.body; - -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/request/body/RandomAccessBody.java b/api/src/main/java/org/asynchttpclient/request/body/RandomAccessBody.java deleted file mode 100644 index 7dbf616c97..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/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.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 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/request/body/generator/ByteArrayBodyGenerator.java b/api/src/main/java/org/asynchttpclient/request/body/generator/ByteArrayBodyGenerator.java deleted file mode 100644 index 3bee463da2..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/generator/ByteArrayBodyGenerator.java +++ /dev/null @@ -1,70 +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.request.body.generator; - -import java.io.IOException; -import java.nio.ByteBuffer; - -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; - } - - 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() { - return new ByteBody(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/request/body/generator/FeedableBodyGenerator.java b/api/src/main/java/org/asynchttpclient/request/body/generator/FeedableBodyGenerator.java deleted file mode 100644 index f83062e706..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/generator/FeedableBodyGenerator.java +++ /dev/null @@ -1,122 +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.request.body.generator; - -import static java.nio.charset.StandardCharsets.US_ASCII; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicInteger; - -import org.asynchttpclient.request.body.Body; - -/** - * {@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 final class FeedableBodyGenerator implements BodyGenerator { - private final static byte[] END_PADDING = "\r\n".getBytes(US_ASCII); - private final static byte[] ZERO = "0".getBytes(US_ASCII); - private final Queue queue = new ConcurrentLinkedQueue<>(); - private final AtomicInteger queueSize = new AtomicInteger(); - private FeedListener listener; - - @Override - public Body createBody() { - 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); - if (size != 0) { - buffer.put(Integer.toHexString(size).getBytes(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() { - } - - } - - 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/api/src/main/java/org/asynchttpclient/request/body/generator/InputStreamBodyGenerator.java b/api/src/main/java/org/asynchttpclient/request/body/generator/InputStreamBodyGenerator.java deleted file mode 100644 index f3fa9f02d6..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/generator/InputStreamBodyGenerator.java +++ /dev/null @@ -1,133 +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.request.body.generator; - -import org.asynchttpclient.request.body.Body; -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 final class InputStreamBodyGenerator implements BodyGenerator { - - private final static byte[] END_PADDING = "\r\n".getBytes(); - private final static byte[] ZERO = "0".getBytes(); - private static final Logger LOGGER = LoggerFactory.getLogger(InputStreamBody.class); - private final InputStream inputStream; - private boolean patchNetty3ChunkingIssue = false; - - public InputStreamBodyGenerator(InputStream inputStream) { - this.inputStream = inputStream; - } - - public InputStream getInputStream() { - return inputStream; - } - - /** - * {@inheritDoc} - */ - @Override - public Body createBody() { - return new InputStreamBody(inputStream); - } - - private class InputStreamBody implements Body { - - private final InputStream inputStream; - private boolean eof = false; - private int endDataCount = 0; - private byte[] chunk; - - private InputStreamBody(InputStream inputStream) { - this.inputStream = inputStream; - } - - 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 (patchNetty3ChunkingIssue) { - if (read == -1) { - // Since we are chunked, we must output extra bytes before considering the input stream closed. - // chunking requires to end the chunking: - // - A Terminating chunk of "0\r\n".getBytes(), - // - Then a separate packet of "\r\n".getBytes() - if (!eof) { - endDataCount++; - if (endDataCount == 2) - eof = true; - - if (endDataCount == 1) - buffer.put(ZERO); - - buffer.put(END_PADDING); - - return buffer.position(); - } else { - eof = false; - } - return -1; - } - - /** - * Netty 3.2.3 doesn't support chunking encoding properly, so we chunk encoding ourself. - */ - - buffer.put(Integer.toHexString(read).getBytes()); - // Chunking is separated by "\r\n" - buffer.put(END_PADDING); - buffer.put(chunk, 0, read); - // Was missing the final chunk \r\n. - buffer.put(END_PADDING); - } else if (read > 0) { - buffer.put(chunk, 0, read); - } - return read; - } - - public void close() throws IOException { - inputStream.close(); - } - } - - /** - * HACK: This is required because Netty has issues with chunking. - * - * @param patchNettyChunkingIssue - */ - public void patchNetty3ChunkingIssue(boolean patchNetty3ChunkingIssue) { - this.patchNetty3ChunkingIssue = patchNetty3ChunkingIssue; - } -} diff --git a/api/src/main/java/org/asynchttpclient/request/body/multipart/AbstractFilePart.java b/api/src/main/java/org/asynchttpclient/request/body/multipart/AbstractFilePart.java deleted file mode 100644 index 9ebe0e8214..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/multipart/AbstractFilePart.java +++ /dev/null @@ -1,121 +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.request.body.multipart; - -import static java.nio.charset.StandardCharsets.*; - -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(getCharset() != null ? getCharset() : 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); - visitCustomHeaders(visitor); - visitEndOfHeaders(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/request/body/multipart/ByteArrayPart.java b/api/src/main/java/org/asynchttpclient/request/body/multipart/ByteArrayPart.java deleted file mode 100644 index c81f609a57..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/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.request.body.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/request/body/multipart/CounterPartVisitor.java b/api/src/main/java/org/asynchttpclient/request/body/multipart/CounterPartVisitor.java deleted file mode 100644 index b3ec0e558d..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/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.request.body.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/request/body/multipart/FilePart.java b/api/src/main/java/org/asynchttpclient/request/body/multipart/FilePart.java deleted file mode 100644 index 7e74b732e0..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/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.request.body.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/request/body/multipart/FilePartStallHandler.java b/api/src/main/java/org/asynchttpclient/request/body/multipart/FilePartStallHandler.java deleted file mode 100644 index 386dffd13e..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/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.request.body.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/request/body/multipart/FileUploadStalledException.java b/api/src/main/java/org/asynchttpclient/request/body/multipart/FileUploadStalledException.java deleted file mode 100644 index 8dcf9c7771..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/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.request.body.multipart; - -import java.io.IOException; - -/** - * @author Gail Hernandez - */ -@SuppressWarnings("serial") -public class FileUploadStalledException extends IOException { -} diff --git a/api/src/main/java/org/asynchttpclient/request/body/multipart/MultipartBody.java b/api/src/main/java/org/asynchttpclient/request/body/multipart/MultipartBody.java deleted file mode 100644 index 5ab24f9ff9..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/multipart/MultipartBody.java +++ /dev/null @@ -1,244 +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.request.body.multipart; - -import org.asynchttpclient.request.body.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; - } - - public byte[] getBoundary() { - return boundary; - } - - // 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/request/body/multipart/MultipartUtils.java b/api/src/main/java/org/asynchttpclient/request/body/multipart/MultipartUtils.java deleted file mode 100644 index 9b232e2e4d..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/multipart/MultipartUtils.java +++ /dev/null @@ -1,201 +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.request.body.multipart; - -import static java.nio.charset.StandardCharsets.US_ASCII; -import static org.asynchttpclient.request.body.multipart.Part.CRLF_BYTES; -import static org.asynchttpclient.request.body.multipart.Part.EXTRA_BYTES; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -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; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.util.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -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(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(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 = StringUtils.stringBuilder().append(base); - if (!base.endsWith(";")) - buffer.append(';'); - return buffer.append(" boundary=").append(new String(multipartBoundary, 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/request/body/multipart/OutputStreamPartVisitor.java b/api/src/main/java/org/asynchttpclient/request/body/multipart/OutputStreamPartVisitor.java deleted file mode 100644 index 849e41d282..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/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.request.body.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/request/body/multipart/Part.java b/api/src/main/java/org/asynchttpclient/request/body/multipart/Part.java deleted file mode 100644 index 4c439ed64f..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/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.request.body.multipart; - -import static java.nio.charset.StandardCharsets.*; - -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/request/body/multipart/PartBase.java b/api/src/main/java/org/asynchttpclient/request/body/multipart/PartBase.java deleted file mode 100644 index b0ba8110f8..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/multipart/PartBase.java +++ /dev/null @@ -1,259 +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.request.body.multipart; - -import static java.nio.charset.StandardCharsets.US_ASCII; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; - -import org.asynchttpclient.Param; - -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 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 visitCustomHeaders(PartVisitor visitor) throws IOException { - if (isNonEmpty(customHeaders)) { - for (Param param: customHeaders) { - visitor.withBytes(CRLF_BYTES); - visitor.withBytes(param.getName().getBytes(US_ASCII)); - visitor.withBytes(param.getValue().getBytes(US_ASCII)); - } - } - } - - protected void visitEndOfHeaders(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); - visitCustomHeaders(visitor); - visitEndOfHeaders(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); - visitCustomHeaders(visitor); - visitEndOfHeaders(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; - } - - public void addCustomHeader(String name, String value) { - if (customHeaders == null) { - customHeaders = new ArrayList(2); - } - customHeaders.add(new Param(name, value)); - } -} diff --git a/api/src/main/java/org/asynchttpclient/request/body/multipart/PartVisitor.java b/api/src/main/java/org/asynchttpclient/request/body/multipart/PartVisitor.java deleted file mode 100644 index 0ab6a86ae8..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/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.request.body.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/request/body/multipart/StringPart.java b/api/src/main/java/org/asynchttpclient/request/body/multipart/StringPart.java deleted file mode 100644 index e41faac95f..0000000000 --- a/api/src/main/java/org/asynchttpclient/request/body/multipart/StringPart.java +++ /dev/null @@ -1,122 +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.request.body.multipart; - -import static java.nio.charset.StandardCharsets.*; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.channels.WritableByteChannel; -import java.nio.charset.Charset; - -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 = 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 final String value; - - private static Charset charsetOrDefault(Charset charset) { - return charset == null ? DEFAULT_CHARSET : charset; - } - - private static String contentTypeOrDefault(String contentType) { - return contentType == null ? DEFAULT_CONTENT_TYPE : contentType; - } - - private static String transferEncodingOrDefault(String transferEncoding) { - return transferEncoding == null ? DEFAULT_TRANSFER_ENCODING : transferEncoding; - } - - 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, contentTypeOrDefault(contentType), charsetOrDefault(charset), contentId, transferEncodingOrDefault(transferEncoding)); - 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(getCharset()); - this.value = value; - } - - /** - * 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)); - } - - public String getValue() { - return value; - } -} 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 a9aa16eb2b..0000000000 --- a/api/src/main/java/org/asynchttpclient/simple/SimpleAHCTransferListener.java +++ /dev/null @@ -1,79 +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.uri.Uri; - -/** - * 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.simple.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(Uri 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(Uri 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(Uri 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(Uri 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(Uri uri, int statusCode, String statusText); -} - diff --git a/api/src/main/java/org/asynchttpclient/simple/SimpleAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/simple/SimpleAsyncHttpClient.java deleted file mode 100644 index ef9ea74e59..0000000000 --- a/api/src/main/java/org/asynchttpclient/simple/SimpleAsyncHttpClient.java +++ /dev/null @@ -1,872 +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.simple; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; - -import java.io.Closeable; -import java.io.IOException; -import java.nio.charset.Charset; -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.AsyncCompletionHandlerBase; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.DefaultAsyncHttpClient; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Param; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.handler.ProgressAsyncHandler; -import org.asynchttpclient.handler.resumable.ResumableAsyncHandler; -import org.asynchttpclient.handler.resumable.ResumableIOExceptionFilter; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.request.body.generator.BodyGenerator; -import org.asynchttpclient.request.body.multipart.Part; -import org.asynchttpclient.simple.consumer.BodyConsumer; -import org.asynchttpclient.simple.consumer.ResumableBodyConsumer; -import org.asynchttpclient.uri.Uri; - -/** - * 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 SimpleAsyncHttpClient(AsyncHttpClientConfig config, RequestBuilder requestBuilder, ThrowableHandler defaultThrowableHandler, - ErrorDocumentBehaviour errorDocumentBehaviour, boolean resumeEnabled, AsyncHttpClient ahc, SimpleAHCTransferListener listener) { - this.config = config; - this.requestBuilder = requestBuilder; - this.defaultThrowableHandler = defaultThrowableHandler; - this.resumeEnabled = resumeEnabled; - this.errorDocumentBehaviour = errorDocumentBehaviour; - this.asyncHttpClient = ahc; - this.listener = listener; - - 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) { - asyncHttpClient = new DefaultAsyncHttpClient(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; - - 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 setConnectTimeout(int connectTimeuot) { - configBuilder.setConnectTimeout(connectTimeuot); - 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 setCompressionEnforced(boolean compressionEnforced) { - configBuilder.setCompressionEnforced(compressionEnforced); - 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 setRealmCharset(Charset charset) { - realm().setCharset(charset); - 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 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); - - 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 onHeadersWritten() { - return delegate.onHeadersWritten(); - } - - public AsyncHandler.State onContentWritten() { - return delegate.onContentWritten(); - } - - 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 Uri 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, Uri 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(Uri 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/simple/ThrowableHandler.java b/api/src/main/java/org/asynchttpclient/simple/ThrowableHandler.java deleted file mode 100644 index 020db9bc36..0000000000 --- a/api/src/main/java/org/asynchttpclient/simple/ThrowableHandler.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.simple; - - -/** - * 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/simple/consumer/AppendableBodyConsumer.java b/api/src/main/java/org/asynchttpclient/simple/consumer/AppendableBodyConsumer.java deleted file mode 100644 index fa9657566a..0000000000 --- a/api/src/main/java/org/asynchttpclient/simple/consumer/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.simple.consumer; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.io.Closeable; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; - -/** - * An {@link Appendable} customer for {@link ByteBuffer} - */ -public class AppendableBodyConsumer implements BodyConsumer { - - private final Appendable appendable; - private final Charset charset; - - public AppendableBodyConsumer(Appendable appendable, Charset charset) { - this.appendable = appendable; - this.charset = charset; - } - - public AppendableBodyConsumer(Appendable appendable) { - this.appendable = appendable; - this.charset = UTF_8; - } - - @Override - public void consume(ByteBuffer byteBuffer) throws IOException { - appendable - .append(new String(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), byteBuffer.remaining(), charset)); - } - - @Override - public void close() throws IOException { - if (appendable instanceof Closeable) { - Closeable.class.cast(appendable).close(); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/simple/consumer/BodyConsumer.java b/api/src/main/java/org/asynchttpclient/simple/consumer/BodyConsumer.java deleted file mode 100644 index 4aede52633..0000000000 --- a/api/src/main/java/org/asynchttpclient/simple/consumer/BodyConsumer.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.simple.consumer; - -import java.io.Closeable; -import java.io.IOException; -import java.nio.ByteBuffer; - -import org.asynchttpclient.simple.SimpleAsyncHttpClient; - -/** - * 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/simple/consumer/ByteBufferBodyConsumer.java b/api/src/main/java/org/asynchttpclient/simple/consumer/ByteBufferBodyConsumer.java deleted file mode 100644 index 9ebcf75d8f..0000000000 --- a/api/src/main/java/org/asynchttpclient/simple/consumer/ByteBufferBodyConsumer.java +++ /dev/null @@ -1,44 +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.consumer; - -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/simple/consumer/FileBodyConsumer.java b/api/src/main/java/org/asynchttpclient/simple/consumer/FileBodyConsumer.java deleted file mode 100644 index fd03e110a5..0000000000 --- a/api/src/main/java/org/asynchttpclient/simple/consumer/FileBodyConsumer.java +++ /dev/null @@ -1,62 +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.simple.consumer; - -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/simple/consumer/OutputStreamBodyConsumer.java b/api/src/main/java/org/asynchttpclient/simple/consumer/OutputStreamBodyConsumer.java deleted file mode 100644 index de3a02d5ba..0000000000 --- a/api/src/main/java/org/asynchttpclient/simple/consumer/OutputStreamBodyConsumer.java +++ /dev/null @@ -1,45 +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.simple.consumer; - -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/simple/consumer/ResumableBodyConsumer.java b/api/src/main/java/org/asynchttpclient/simple/consumer/ResumableBodyConsumer.java deleted file mode 100644 index a45ca05f19..0000000000 --- a/api/src/main/java/org/asynchttpclient/simple/consumer/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.simple.consumer; - -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/spnego/SpnegoEngine.java b/api/src/main/java/org/asynchttpclient/spnego/SpnegoEngine.java deleted file mode 100644 index 205bc4bbec..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 SpnegoEngineException { - 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 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 = 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 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 ex) { - throw new SpnegoEngineException(ex.getMessage()); - } - } -} diff --git a/api/src/main/java/org/asynchttpclient/spnego/SpnegoEngineException.java b/api/src/main/java/org/asynchttpclient/spnego/SpnegoEngineException.java deleted file mode 100644 index d7e89dff95..0000000000 --- a/api/src/main/java/org/asynchttpclient/spnego/SpnegoEngineException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2015 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.spnego; - -/** - * Signals SPNEGO protocol failure. - */ -public class SpnegoEngineException extends RuntimeException { - - private static final long serialVersionUID = -3123799505052881438L; - - public SpnegoEngineException(String message) { - super(message); - } - - public SpnegoEngineException(String message, Throwable cause) { - super(message, cause); - } -} \ No newline at end of file diff --git a/api/src/main/java/org/asynchttpclient/uri/Uri.java b/api/src/main/java/org/asynchttpclient/uri/Uri.java deleted file mode 100644 index ccd3046824..0000000000 --- a/api/src/main/java/org/asynchttpclient/uri/Uri.java +++ /dev/null @@ -1,201 +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; -import org.asynchttpclient.util.StringUtils; - -public class Uri { - - public static Uri create(String originalUrl) { - return create(null, originalUrl); - } - - public static Uri create(Uri context, final String originalUrl) { - UriParser parser = new UriParser(); - parser.parse(context, originalUrl); - - return new Uri(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; - private String url; - - public Uri(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 toJavaNetURI() throws URISyntaxException { - return new URI(toUrl()); - } - - public String toUrl() { - if (url == null) { - StringBuilder sb = StringUtils.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; - } - - public String toRelativeUrl() { - StringBuilder sb = StringUtils.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 Uri withNewScheme(String newScheme) { - return new Uri(newScheme,// - userInfo,// - host,// - port,// - path,// - query); - } - - public Uri withNewQuery(String newQuery) { - return new Uri(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; - 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; - return true; - } -} diff --git a/api/src/main/java/org/asynchttpclient/uri/UriParser.java b/api/src/main/java/org/asynchttpclient/uri/UriParser.java deleted file mode 100644 index a480a96fd0..0000000000 --- a/api/src/main/java/org/asynchttpclient/uri/UriParser.java +++ /dev/null @@ -1,342 +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 UriParser { - - 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(Uri 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(Uri 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 if (end == 0) { - break; - } - } 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(Uri 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 9d74704525..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ /dev/null @@ -1,137 +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 java.nio.charset.StandardCharsets.ISO_8859_1; -import static org.asynchttpclient.util.MiscUtils.*; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.List; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Param; -import org.asynchttpclient.Request; -import org.asynchttpclient.uri.Uri; - -/** - * {@link org.asynchttpclient.AsyncHttpProvider} common utilities. - */ -public class AsyncHttpProviderUtils { - - public static final IOException REMOTELY_CLOSED_EXCEPTION = buildStaticIOException("Remotely closed"); - public static final IOException CHANNEL_CLOSED_EXCEPTION = buildStaticIOException("Channel closed"); - - public final static Charset DEFAULT_CHARSET = ISO_8859_1; - - public static final 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'"); - } - } - - public final static String getBaseUrl(Uri uri) { - return uri.getScheme() + "://" + getAuthority(uri); - } - - public final static String getAuthority(Uri uri) { - int port = uri.getPort() != -1 ? uri.getPort() : getExplicitPort(uri); - return uri.getHost() + ":" + port; - } - - public final static boolean isSameBase(Uri uri1, Uri uri2) { - return uri1.getScheme().equals(uri2.getScheme()) && uri1.getHost().equals(uri2.getHost()) && getExplicitPort(uri1) == getExplicitPort(uri2); - } - - public static final int getSchemeDefaultPort(String scheme) { - return scheme.equals("http") || scheme.equals("ws") ? 80 : 443; - } - - public static final int getExplicitPort(Uri uri) { - int port = uri.getPort(); - if (port == -1) - port = getSchemeDefaultPort(uri.getScheme()); - return port; - } - - /** - * Convenient for HTTP layer when targeting server root - * - * @return the raw path or "/" if it's null - */ - public final static String getNonEmptyPath(Uri uri) { - return isNonEmpty(uri.getPath()) ? uri.getPath() : "/"; - } - - public static Charset 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("'") - String charsetName = charset.replaceAll("\"", "").replaceAll("'", ""); - return Charset.forName(charsetName); - } - } - } - return null; - } - - public static int requestTimeout(AsyncHttpClientConfig config, Request request) { - return request.getRequestTimeout() != 0 ? request.getRequestTimeout() : config.getRequestTimeout(); - } - - public static boolean followRedirect(AsyncHttpClientConfig config, Request request) { - return request.getFollowRedirect() != null ? request.getFollowRedirect().booleanValue() : config.isFollowRedirect(); - } - - private static StringBuilder urlEncodeFormParams0(List params) { - StringBuilder sb = StringUtils.stringBuilder(); - for (Param param : params) { - encodeAndAppendFormParam(sb, param.getName(), param.getValue()); - } - sb.setLength(sb.length() - 1); - return sb; - } - - public static ByteBuffer urlEncodeFormParams(List params, Charset charset) { - return StringUtils.charSequence2ByteBuffer(urlEncodeFormParams0(params), charset); - } - - private static void encodeAndAppendFormParam(final StringBuilder sb, final CharSequence name, final CharSequence value) { - Utf8UrlEncoder.encodeAndAppendFormElement(sb, name); - if (value != null) { - sb.append('='); - Utf8UrlEncoder.encodeAndAppendFormElement(sb, value); - } - sb.append('&'); - } - - public static String hostHeader(Request request, Uri uri) { - String virtualHost = request.getVirtualHost(); - if (virtualHost != null) - return virtualHost; - else { - String host = uri.getHost(); - int port = uri.getPort(); - return port == -1 || port == getSchemeDefaultPort(uri.getScheme()) ? host : host + ":" + port; - } - } -} 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 27b81362f1..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java +++ /dev/null @@ -1,89 +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 java.nio.charset.StandardCharsets.ISO_8859_1; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getNonEmptyPath; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - -import java.nio.charset.Charset; - -import org.asynchttpclient.Realm; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.uri.Uri; - -public final class AuthenticatorUtils { - - public static String computeBasicAuthentication(Realm realm) { - return computeBasicAuthentication(realm.getPrincipal(), realm.getPassword(), realm.getCharset()); - } - - public static String computeBasicAuthentication(ProxyServer proxyServer) { - return computeBasicAuthentication(proxyServer.getPrincipal(), proxyServer.getPassword(), proxyServer.getCharset()); - } - - private static String computeBasicAuthentication(String principal, String password, Charset charset) { - String s = principal + ":" + password; - return "Basic " + Base64.encode(s.getBytes(charset)); - } - - public static String computeRealmURI(Realm realm) { - return computeRealmURI(realm.getUri(), realm.isUseAbsoluteURI(), realm.isOmitQuery()); - } - - public static String computeRealmURI(Uri uri, boolean useAbsoluteURI, boolean omitQuery) { - if (useAbsoluteURI) { - return omitQuery && MiscUtils.isNonEmpty(uri.getQuery()) ? uri.withNewQuery(null).toUrl() : uri.toUrl(); - } else { - String path = getNonEmptyPath(uri); - return omitQuery || !MiscUtils.isNonEmpty(uri.getQuery()) ? path : path + "?" + uri.getQuery(); - } - } - - public static String computeDigestAuthentication(Realm realm) { - - 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); - 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)); - } - - 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/HttpUtils.java b/api/src/main/java/org/asynchttpclient/util/HttpUtils.java deleted file mode 100644 index 306a7a9fc5..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/HttpUtils.java +++ /dev/null @@ -1,43 +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 org.asynchttpclient.uri.Uri; - -public final class HttpUtils { - - 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 HttpUtils() { - } - - public static boolean isWebSocket(String scheme) { - return WS.equals(scheme) || WSS.equalsIgnoreCase(scheme); - } - - public static boolean isSecure(String scheme) { - return HTTPS.equals(scheme) || WSS.equals(scheme); - } - - public static boolean isSecure(Uri uri) { - return isSecure(uri.getScheme()); - } - - public static boolean useProxyConnect(Uri uri) { - return isSecure(uri) || isWebSocket(uri.getScheme()); - } -} 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 75de534686..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/MiscUtils.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.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) { - } - } - - public static IOException buildStaticIOException(String message) { - IOException ioe = new IOException(message); - ioe.setStackTrace(new StackTraceElement[] {}); - return ioe; - } -} 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 221f0317c7..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java +++ /dev/null @@ -1,249 +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.Request; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.proxy.ProxyServerSelector; -import org.asynchttpclient.proxy.ProxyServer.Protocol; -import org.asynchttpclient.uri.Uri; -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(Uri 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)) { - 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(Uri uri) { - return proxyServer; - } - }; - } -} 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 5d31125d15..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/SslUtils.java +++ /dev/null @@ -1,81 +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.security.GeneralSecurityException; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.asynchttpclient.AsyncHttpClientConfig; - -/** - * 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(AsyncHttpClientConfig config) throws GeneralSecurityException { - SSLContext sslContext = config.getSSLContext(); - - if (sslContext == null) { - sslContext = config.isAcceptAnyCertificate() ? looseTrustManagerSSLContext : SSLContext.getDefault(); - if (config.getSslSessionCacheSize() != null) - sslContext.getClientSessionContext().setSessionCacheSize(config.getSslSessionCacheSize()); - if (config.getSslSessionTimeout() != null) - sslContext.getClientSessionContext().setSessionTimeout(config.getSslSessionTimeout()); - } - return sslContext; - } -} 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/StringUtils.java b/api/src/main/java/org/asynchttpclient/util/StringUtils.java deleted file mode 100644 index 10234d87ae..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/StringUtils.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.util; - -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; - -public final class StringUtils { - - private static final ThreadLocal STRING_BUILDERS = new ThreadLocal() { - protected StringBuilder initialValue() { - return new StringBuilder(512); - }; - }; - - /** - * BEWARE: MUSN'T APPEND TO ITSELF! - * @return a pooled StringBuilder - */ - public static StringBuilder stringBuilder() { - StringBuilder sb = STRING_BUILDERS.get(); - sb.setLength(0); - return sb; - } - - private StringUtils() { - // unused - } - - 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); - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/UriEncoder.java b/api/src/main/java/org/asynchttpclient/util/UriEncoder.java deleted file mode 100644 index 42b6a429a7..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/UriEncoder.java +++ /dev/null @@ -1,146 +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 static org.asynchttpclient.util.MiscUtils.isNonEmpty; -import static org.asynchttpclient.util.Utf8UrlEncoder.encodeAndAppendQuery; - -import org.asynchttpclient.Param; -import org.asynchttpclient.uri.Uri; - -import java.util.List; - -public enum UriEncoder { - - FIXING { - - public String encodePath(String path) { - return Utf8UrlEncoder.encodePath(path); - } - - private void encodeAndAppendQueryParam(final StringBuilder sb, final CharSequence name, final 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()); - } - - protected String withQueryWithParams(final String query, final List queryParams) { - // concatenate encoded query + encoded query params - StringBuilder sb = StringUtils.stringBuilder(); - encodeAndAppendQuery(sb, query); - sb.append('&'); - encodeAndAppendQueryParams(sb, queryParams); - sb.setLength(sb.length() - 1); - return sb.toString(); - } - - protected String withQueryWithoutParams(final String query) { - // encode query - StringBuilder sb = StringUtils.stringBuilder(); - encodeAndAppendQuery(sb, query); - return sb.toString(); - } - - protected String withoutQueryWithParams(final List queryParams) { - // concatenate encoded query params - StringBuilder sb = StringUtils.stringBuilder(); - encodeAndAppendQueryParams(sb, queryParams); - sb.setLength(sb.length() - 1); - return sb.toString(); - } - }, // - - RAW { - - public String encodePath(String path) { - return path; - } - - private void appendRawQueryParam(StringBuilder sb, String name, 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()); - } - - protected String withQueryWithParams(final String query, final List queryParams) { - // concatenate raw query + raw query params - StringBuilder sb = StringUtils.stringBuilder(); - sb.append(query); - appendRawQueryParams(sb, queryParams); - sb.setLength(sb.length() - 1); - return sb.toString(); - } - - protected String withQueryWithoutParams(final String query) { - // return raw query as is - return query; - } - - protected String withoutQueryWithParams(final List queryParams) { - // concatenate raw queryParams - StringBuilder sb = StringUtils.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(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 Uri encode(Uri uri, 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); - } - - protected abstract String encodePath(String path); - - private final String encodeQuery(final String query, final List queryParams) { - return isNonEmpty(query) ? withQuery(query, queryParams) : withoutQuery(queryParams); - } -} 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 6a4c04cfce..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 f74dc469cb..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/Utf8UrlEncoder.java +++ /dev/null @@ -1,175 +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.util.BitSet; - -/** - * Convenience class that encapsulates details of "percent encoding" - * (as per RFC-3986, see [http://www.ietf.org/rfc/rfc3986.txt]). - */ -public final class Utf8UrlEncoder { - - /** - * Encoding table used for figuring out ascii characters that must be escaped - * (all non-Ascii characters need to be encoded anyway) - */ - public final static BitSet RFC3986_UNRESERVED_CHARS = new BitSet(256); - public final static BitSet RFC3986_RESERVED_CHARS = new BitSet(256); - public final static BitSet RFC3986_SUBDELIM_CHARS = new BitSet(256); - public final static BitSet BUILT_PATH_UNTOUCHED_CHARS = new BitSet(256); - public final static BitSet BUILT_QUERY_UNTOUCHED_CHARS = new BitSet(256); - // http://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm - public final static BitSet FORM_URL_ENCODED_SAFE_CHARS = new BitSet(256); - - static { - for (int i = 'a'; i <= 'z'; ++i) { - RFC3986_UNRESERVED_CHARS.set(i); - FORM_URL_ENCODED_SAFE_CHARS.set(i); - } - for (int i = 'A'; i <= 'Z'; ++i) { - RFC3986_UNRESERVED_CHARS.set(i); - FORM_URL_ENCODED_SAFE_CHARS.set(i); - } - for (int i = '0'; i <= '9'; ++i) { - RFC3986_UNRESERVED_CHARS.set(i); - FORM_URL_ENCODED_SAFE_CHARS.set(i); - } - RFC3986_UNRESERVED_CHARS.set('-'); - RFC3986_UNRESERVED_CHARS.set('.'); - RFC3986_UNRESERVED_CHARS.set('_'); - RFC3986_UNRESERVED_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(';'); - RFC3986_SUBDELIM_CHARS.set('='); - - FORM_URL_ENCODED_SAFE_CHARS.set('-'); - FORM_URL_ENCODED_SAFE_CHARS.set('.'); - FORM_URL_ENCODED_SAFE_CHARS.set('_'); - FORM_URL_ENCODED_SAFE_CHARS.set('*'); - - RFC3986_RESERVED_CHARS.set('!'); - RFC3986_RESERVED_CHARS.set('*'); - RFC3986_RESERVED_CHARS.set('\''); - RFC3986_RESERVED_CHARS.set('('); - RFC3986_RESERVED_CHARS.set(')'); - RFC3986_RESERVED_CHARS.set(';'); - RFC3986_RESERVED_CHARS.set(':'); - RFC3986_RESERVED_CHARS.set('@'); - RFC3986_RESERVED_CHARS.set('&'); - RFC3986_RESERVED_CHARS.set('='); - RFC3986_RESERVED_CHARS.set('+'); - RFC3986_RESERVED_CHARS.set('$'); - RFC3986_RESERVED_CHARS.set(','); - RFC3986_RESERVED_CHARS.set('/'); - RFC3986_RESERVED_CHARS.set('?'); - RFC3986_RESERVED_CHARS.set('#'); - RFC3986_RESERVED_CHARS.set('['); - RFC3986_RESERVED_CHARS.set(']'); - - BUILT_PATH_UNTOUCHED_CHARS.or(RFC3986_UNRESERVED_CHARS); - BUILT_PATH_UNTOUCHED_CHARS.set('%'); - BUILT_PATH_UNTOUCHED_CHARS.or(RFC3986_SUBDELIM_CHARS); - BUILT_PATH_UNTOUCHED_CHARS.set(':'); - BUILT_PATH_UNTOUCHED_CHARS.set('@'); - BUILT_PATH_UNTOUCHED_CHARS.set('/'); - - BUILT_QUERY_UNTOUCHED_CHARS.or(RFC3986_UNRESERVED_CHARS); - BUILT_QUERY_UNTOUCHED_CHARS.or(RFC3986_RESERVED_CHARS); - BUILT_QUERY_UNTOUCHED_CHARS.set('%'); - } - - private static final char[] HEX = "0123456789ABCDEF".toCharArray(); - - private Utf8UrlEncoder() { - } - - public static String encodePath(String input) { - StringBuilder sb = new StringBuilder(input.length() + 6); - appendEncoded(sb, input, BUILT_PATH_UNTOUCHED_CHARS, false); - return 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, RFC3986_UNRESERVED_CHARS, false); - } - - public static StringBuilder encodeAndAppendFormElement(StringBuilder sb, CharSequence input) { - return appendEncoded(sb, input, FORM_URL_ENCODED_SAFE_CHARS, true); - } - - 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 final 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 final 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/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java b/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java deleted file mode 100644 index ca11a3e59c..0000000000 --- a/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java +++ /dev/null @@ -1,307 +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.net.SocketAddress; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -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.Uri; -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 getResponseBody(Charset charset) throws IOException { - return wrappedResponse.getResponseBody(charset); - } - - @Override - public String getResponseBody() throws IOException { - return wrappedResponse.getResponseBody(); - } - - @Override - public Uri 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 SocketAddress getRemoteAddress() { - return wrappedResponse.getRemoteAddress(); - } - - @Override - public SocketAddress getLocalAddress() { - return wrappedResponse.getLocalAddress(); - } - }; - } - - @Override - public int getStatusCode() { - return (statusText == null ? wrapped.getStatusCode() : statusCode); - } - - @Override - public String getStatusText() { - return (statusText == null ? wrapped.getStatusText() : statusText); - } - - @Override - public String getProtocolName() { - return wrapped.getProtocolName(); - } - - @Override - public int getProtocolMajorVersion() { - return wrapped.getProtocolMajorVersion(); - } - - @Override - public int getProtocolMinorVersion() { - return wrapped.getProtocolMinorVersion(); - } - - @Override - public String getProtocolText() { - return wrapped.getStatusText(); - } - - @Override - public SocketAddress getRemoteAddress() { - return wrapped.getRemoteAddress(); - } - - @Override - public SocketAddress getLocalAddress() { - return wrapped.getLocalAddress(); - } - } - - 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 b34ee1eedd..0000000000 --- a/api/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java +++ /dev/null @@ -1,121 +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.net.SocketAddress; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.List; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.Response; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.uri.Uri; -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 getResponseBody() throws IOException { - return response.getResponseBody(); - } - - public String getResponseBody(Charset charset) throws IOException { - return response.getResponseBody(charset); - } - - public Uri 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 SocketAddress getRemoteAddress() { - return response.getRemoteAddress(); - } - - public SocketAddress getLocalAddress() { - return response.getLocalAddress(); - } - - public Document getBodyAsXML() { - return document; - } -} diff --git a/api/src/main/java/org/asynchttpclient/ws/DefaultWebSocketListener.java b/api/src/main/java/org/asynchttpclient/ws/DefaultWebSocketListener.java deleted file mode 100644 index f95dcdbf65..0000000000 --- a/api/src/main/java/org/asynchttpclient/ws/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.ws; - -/** - * 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/ws/UpgradeHandler.java b/api/src/main/java/org/asynchttpclient/ws/UpgradeHandler.java deleted file mode 100644 index 288e5ecf0a..0000000000 --- a/api/src/main/java/org/asynchttpclient/ws/UpgradeHandler.java +++ /dev/null @@ -1,39 +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.ws; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpProvider; - -/** - * Invoked when an {@link AsyncHandler.State#UPGRADE} is returned. Currently the library only support {@link org.asynchttpclient.ws.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/ws/WebSocket.java b/api/src/main/java/org/asynchttpclient/ws/WebSocket.java deleted file mode 100644 index b8513dfc04..0000000000 --- a/api/src/main/java/org/asynchttpclient/ws/WebSocket.java +++ /dev/null @@ -1,131 +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.ws; - -import java.io.Closeable; -import java.net.SocketAddress; - -/** - * A Websocket client - */ -public interface WebSocket extends Closeable { - - /** - * 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 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 sendMessage(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 stream(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/ws/WebSocketByteFragmentListener.java b/api/src/main/java/org/asynchttpclient/ws/WebSocketByteFragmentListener.java deleted file mode 100644 index 2b751c52fa..0000000000 --- a/api/src/main/java/org/asynchttpclient/ws/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.ws; - -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/ws/WebSocketByteListener.java b/api/src/main/java/org/asynchttpclient/ws/WebSocketByteListener.java deleted file mode 100644 index bbe47bce1b..0000000000 --- a/api/src/main/java/org/asynchttpclient/ws/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.ws; - -/** - * 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/ws/WebSocketCloseCodeReasonListener.java b/api/src/main/java/org/asynchttpclient/ws/WebSocketCloseCodeReasonListener.java deleted file mode 100644 index 97821779f8..0000000000 --- a/api/src/main/java/org/asynchttpclient/ws/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.ws; - -/** - * 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/ws/WebSocketListener.java b/api/src/main/java/org/asynchttpclient/ws/WebSocketListener.java deleted file mode 100644 index 3816df8d8a..0000000000 --- a/api/src/main/java/org/asynchttpclient/ws/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.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 - */ - 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/ws/WebSocketPingListener.java b/api/src/main/java/org/asynchttpclient/ws/WebSocketPingListener.java deleted file mode 100644 index 24cb8d6c9d..0000000000 --- a/api/src/main/java/org/asynchttpclient/ws/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.ws; - -/** - * 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/ws/WebSocketPongListener.java b/api/src/main/java/org/asynchttpclient/ws/WebSocketPongListener.java deleted file mode 100644 index 74a8d9f90b..0000000000 --- a/api/src/main/java/org/asynchttpclient/ws/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.ws; - -/** - * 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/ws/WebSocketTextFragmentListener.java b/api/src/main/java/org/asynchttpclient/ws/WebSocketTextFragmentListener.java deleted file mode 100644 index 6f0f33ee46..0000000000 --- a/api/src/main/java/org/asynchttpclient/ws/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.ws; - -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/ws/WebSocketTextListener.java b/api/src/main/java/org/asynchttpclient/ws/WebSocketTextListener.java deleted file mode 100644 index 7ba81c25d1..0000000000 --- a/api/src/main/java/org/asynchttpclient/ws/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.ws; - -/** - * 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/ws/WebSocketUpgradeHandler.java b/api/src/main/java/org/asynchttpclient/ws/WebSocketUpgradeHandler.java deleted file mode 100644 index 06af655e1e..0000000000 --- a/api/src/main/java/org/asynchttpclient/ws/WebSocketUpgradeHandler.java +++ /dev/null @@ -1,182 +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.ws; - -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; - -/** - * 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); - } - throw e; - } - - if (webSocket == null) { - throw new NullPointerException("webSocket"); - } - 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/main/java/org/asynchttpclient/ws/WebSocketUtils.java b/api/src/main/java/org/asynchttpclient/ws/WebSocketUtils.java deleted file mode 100644 index ee8b968821..0000000000 --- a/api/src/main/java/org/asynchttpclient/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.ws; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import org.asynchttpclient.util.Base64; - -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("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/api/src/main/resources/ahc-default.properties b/api/src/main/resources/ahc-default.properties deleted file mode 100644 index 887ae05afa..0000000000 --- a/api/src/main/resources/ahc-default.properties +++ /dev/null @@ -1,33 +0,0 @@ -org.asynchttpclient.maxConnections=-1 -org.asynchttpclient.maxConnectionsPerHost=-1 -org.asynchttpclient.connectTimeout=5000 -org.asynchttpclient.pooledConnectionIdleTimeout=60000 -org.asynchttpclient.readTimeout=60000 -org.asynchttpclient.requestTimeout=60000 -org.asynchttpclient.webSocketTimeout=900000 -org.asynchttpclient.connectionTTL=-1 -org.asynchttpclient.followRedirect=false -org.asynchttpclient.maxRedirects=5 -org.asynchttpclient.compressionEnforced=false -org.asynchttpclient.userAgent=NING/1.0 -org.asynchttpclient.ioThreadMultiplier=2 -org.asynchttpclient.enabledProtocols=TLSv1.2, TLSv1.1, TLSv1 -org.asynchttpclient.useProxySelector=false -org.asynchttpclient.useProxyProperties=false -org.asynchttpclient.strict302Handling=false -org.asynchttpclient.allowPoolingConnections=true -org.asynchttpclient.requestCompressionLevel=-1 -org.asynchttpclient.maxRequestRetry=5 -org.asynchttpclient.allowPoolingSslConnections=true -org.asynchttpclient.disableUrlEncodingForBoundRequests=false -org.asynchttpclient.removeQueryParamOnRedirect=true -org.asynchttpclient.acceptAnyCertificate=false -org.asynchttpclient.httpClientCodecMaxInitialLineLength=4096 -org.asynchttpclient.httpClientCodecMaxHeaderSize=8192 -org.asynchttpclient.httpClientCodecMaxChunkSize=8192 -org.asynchttpclient.disableZeroCopy=false -org.asynchttpclient.handshakeTimeout=10000 -org.asynchttpclient.chunkedFileChunkSize=8192 -org.asynchttpclient.webSocketMaxBufferSize=128000000 -org.asynchttpclient.webSocketMaxFrameSize=10240 -org.asynchttpclient.keepEncodingHeader=false diff --git a/api/src/test/java/org/asynchttpclient/AbstractBasicHttpsTest.java b/api/src/test/java/org/asynchttpclient/AbstractBasicHttpsTest.java deleted file mode 100644 index 1b8a624dc8..0000000000 --- a/api/src/test/java/org/asynchttpclient/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; - -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.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/AbstractBasicTest.java b/api/src/test/java/org/asynchttpclient/AbstractBasicTest.java deleted file mode 100644 index b2a439323a..0000000000 --- a/api/src/test/java/org/asynchttpclient/AbstractBasicTest.java +++ /dev/null @@ -1,118 +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.test.TestUtils.addHttpConnector; -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.newJettyHttpServer; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.test.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 { - - @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/AsyncHttpClientDefaultsTest.java b/api/src/test/java/org/asynchttpclient/AsyncHttpClientDefaultsTest.java deleted file mode 100644 index f3617f4821..0000000000 --- a/api/src/test/java/org/asynchttpclient/AsyncHttpClientDefaultsTest.java +++ /dev/null @@ -1,167 +0,0 @@ -package org.asynchttpclient; - -import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.ASYNC_CLIENT_CONFIG_ROOT; - -import org.asynchttpclient.config.AsyncHttpClientConfigDefaults; -import org.asynchttpclient.config.AsyncHttpClientConfigHelper; -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.lang.reflect.Method; - -@Test -public class AsyncHttpClientDefaultsTest { - - public void testDefaultMaxTotalConnections() { - Assert.assertEquals(AsyncHttpClientConfigDefaults.defaultMaxConnections(),-1); - testIntegerSystemProperty("maxConnections", "defaultMaxConnections", "100"); - } - - public void testDefaultMaxConnectionPerHost() { - Assert.assertEquals(AsyncHttpClientConfigDefaults.defaultMaxConnectionsPerHost(), -1); - testIntegerSystemProperty("maxConnectionsPerHost", "defaultMaxConnectionsPerHost", "100"); - } - - public void testDefaultConnectTimeOut() { - Assert.assertEquals(AsyncHttpClientConfigDefaults.defaultConnectTimeout(), 5 * 1000); - testIntegerSystemProperty("connectTimeout", "defaultConnectTimeout", "100"); - } - - public void testDefaultPooledConnectionIdleTimeout() { - Assert.assertEquals(AsyncHttpClientConfigDefaults.defaultPooledConnectionIdleTimeout(), 60 * 1000); - testIntegerSystemProperty("pooledConnectionIdleTimeout", "defaultPooledConnectionIdleTimeout", "100"); - } - - public void testDefaultReadTimeout() { - Assert.assertEquals(AsyncHttpClientConfigDefaults.defaultReadTimeout(), 60 * 1000); - testIntegerSystemProperty("readTimeout", "defaultReadTimeout", "100"); - } - - public void testDefaultRequestTimeout() { - Assert.assertEquals(AsyncHttpClientConfigDefaults.defaultRequestTimeout(), 60 * 1000); - testIntegerSystemProperty("requestTimeout", "defaultRequestTimeout", "100"); - } - - public void testDefaultWebSocketTimeout() { - Assert.assertEquals(AsyncHttpClientConfigDefaults.defaultWebSocketTimeout(), 15 * 60 * 1000); - testIntegerSystemProperty("webSocketTimeout", "defaultWebSocketTimeout", "100"); - } - - public void testDefaultConnectionTTL() { - Assert.assertEquals(AsyncHttpClientConfigDefaults.defaultConnectionTTL(), -1); - testIntegerSystemProperty("connectionTTL", "defaultConnectionTTL", "100"); - } - - public void testDefaultFollowRedirect() { - Assert.assertFalse(AsyncHttpClientConfigDefaults.defaultFollowRedirect()); - testBooleanSystemProperty("followRedirect", "defaultFollowRedirect", "true"); - } - - public void testDefaultMaxRedirects() { - Assert.assertEquals(AsyncHttpClientConfigDefaults.defaultMaxRedirects(), 5); - testIntegerSystemProperty("maxRedirects", "defaultMaxRedirects", "100"); - } - - public void testDefaultCompressionEnforced() { - Assert.assertFalse(AsyncHttpClientConfigDefaults.defaultCompressionEnforced()); - testBooleanSystemProperty("compressionEnforced", "defaultCompressionEnforced", "true"); - } - - public void testDefaultUserAgent() { - Assert.assertEquals(AsyncHttpClientConfigDefaults.defaultUserAgent(),"NING/1.0"); - testStringSystemProperty("userAgent", "defaultUserAgent", "MyAHC"); - } - - public void testDefaultIoThreadMultiplier() { - Assert.assertEquals(AsyncHttpClientConfigDefaults.defaultIoThreadMultiplier(), 2); - testIntegerSystemProperty("ioThreadMultiplier", "defaultIoThreadMultiplier", "100"); - } - - public void testDefaultUseProxySelector() { - Assert.assertFalse(AsyncHttpClientConfigDefaults.defaultUseProxySelector()); - testBooleanSystemProperty("useProxySelector", "defaultUseProxySelector", "true"); - } - - public void testDefaultUseProxyProperties() { - Assert.assertFalse(AsyncHttpClientConfigDefaults.defaultUseProxyProperties()); - testBooleanSystemProperty("useProxyProperties", "defaultUseProxyProperties", "true"); - } - - public void testDefaultStrict302Handling() { - Assert.assertFalse(AsyncHttpClientConfigDefaults.defaultStrict302Handling()); - testBooleanSystemProperty("strict302Handling", "defaultStrict302Handling", "true"); - } - - public void testDefaultAllowPoolingConnection() { - Assert.assertTrue(AsyncHttpClientConfigDefaults.defaultAllowPoolingConnections()); - testBooleanSystemProperty("allowPoolingConnections", "defaultAllowPoolingConnections", "false"); - } - - public void testDefaultMaxRequestRetry() { - Assert.assertEquals(AsyncHttpClientConfigDefaults.defaultMaxRequestRetry(), 5); - testIntegerSystemProperty("maxRequestRetry", "defaultMaxRequestRetry", "100"); - } - - public void testDefaultAllowPoolingSslConnections() { - Assert.assertTrue(AsyncHttpClientConfigDefaults.defaultAllowPoolingSslConnections()); - testBooleanSystemProperty("allowPoolingSslConnections", "defaultAllowPoolingSslConnections", "false"); - } - - public void testDefaultDisableUrlEncodingForBoundRequests() { - Assert.assertFalse(AsyncHttpClientConfigDefaults.defaultDisableUrlEncodingForBoundRequests()); - testBooleanSystemProperty("disableUrlEncodingForBoundRequests", "defaultDisableUrlEncodingForBoundRequests", "true"); - } - - public void testDefaultAcceptAnyCertificate() { - Assert.assertFalse(AsyncHttpClientConfigDefaults.defaultAcceptAnyCertificate()); - testBooleanSystemProperty("acceptAnyCertificate", "defaultAcceptAnyCertificate", "true"); - } - - 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, new Class[]{}); - Assert.assertEquals(method.invoke(null, new Object[]{}),Integer.parseInt(value)); - } catch (Exception e) { - Assert.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 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, new Class[]{}); - Assert.assertEquals(method.invoke(null, new Object[]{}),Boolean.parseBoolean(value)); - } catch (Exception e) { - Assert.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 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, new Class[]{}); - Assert.assertEquals(method.invoke(null, new Object[]{}),value); - } catch (Exception e) { - Assert.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/api/src/test/java/org/asynchttpclient/AsyncProvidersBasicTest.java b/api/src/test/java/org/asynchttpclient/AsyncProvidersBasicTest.java deleted file mode 100755 index b63816e4a9..0000000000 --- a/api/src/test/java/org/asynchttpclient/AsyncProvidersBasicTest.java +++ /dev/null @@ -1,1428 +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 java.nio.charset.StandardCharsets.UTF_8; -import static org.asynchttpclient.test.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; -import static org.asynchttpclient.test.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 java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.ConnectException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.UnknownHostException; -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; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.AsyncHttpClientConfig.Builder; -import org.asynchttpclient.config.AsyncHttpClientConfigBean; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.handler.MaxRedirectException; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.request.body.multipart.Part; -import org.asynchttpclient.request.body.multipart.StringPart; -import org.asynchttpclient.test.EventCollectingHandler; -import org.testng.annotations.Test; - -public abstract class AsyncProvidersBasicTest extends AbstractBasicTest { - - protected abstract AsyncHttpProviderConfig getProviderConfig(); - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncProviderEncodingTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - Request request = new RequestBuilder("GET").setUrl(getTargetUrl() + "?q=+%20x").build(); - assertEquals(request.getUrl(), getTargetUrl() + "?q=+%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=+%20x"); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncProviderEncodingTest2() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void emptyRequestURI() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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()); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncProviderContentLenghtGETTest() throws Exception { - final HttpURLConnection connection = (HttpURLConnection) new URL(getTargetUrl()).openConnection(); - connection.connect(); - final int ct = connection.getContentLength(); - connection.disconnect(); - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncContentTypeGETTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncHeaderGETTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncHeaderPOSTTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncParamPOSTTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncStatusHEADTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - // TODO: fix test - @Test(groups = { "standalone", "default_provider", "async" }, enabled = false) - public void asyncStatusHEADContentLenghtTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(120 * 1000).build())) { - 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"); - } - } - } - - @Test(groups = { "online", "default_provider", "async" }, expectedExceptions = { NullPointerException.class }) - public void asyncNullSchemeTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - client.prepareGet("www.sun.com").execute(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoGetTransferEncodingTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoGetHeadersTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoGetCookieTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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", false, "/", "/", Long.MIN_VALUE, 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostDefaultContentType() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostBodyIsoTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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")); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostBytesTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostInputStreamTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPutInputStreamTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostMultiPartTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - final CountDownLatch l = new CountDownLatch(1); - - Part p = new StringPart("foo", "bar"); - - 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)); - - assertTrue(response.getResponseBody().regionMatches(false, "--".length(), boundary, 0, boundary.length())); - } finally { - l.countDown(); - } - return response; - } - }).get(); - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostBasicGZIPTest() throws Exception { - AsyncHttpClientConfig cf = new AsyncHttpClientConfig.Builder().setCompressionEnforced(true).build(); - try (AsyncHttpClient client = getAsyncHttpClient(cf)) { - 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"), "gzip,deflate"); - } finally { - l.countDown(); - } - return response; - } - }).get(); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostProxyTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setProxyServer(new ProxyServer("127.0.0.1", port2)).build())) { - 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"); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncRequestVirtualServerPOSTTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPutTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostLatchBytesTest() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }, expectedExceptions = { CancellationException.class }) - public void asyncDoPostDelayCancelTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - future.get(TIMEOUT, TimeUnit.SECONDS); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostDelayBytesTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostNullBytesTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostListenerBytesTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncConnectInvalidFuture() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncConnectInvalidPortFuture() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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()); - } - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncConnectInvalidPort() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - // 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); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncConnectInvalidHandlerPort() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "online", "default_provider", "async" }, expectedExceptions = { ConnectException.class, UnresolvedAddressException.class, UnknownHostException.class }) - public void asyncConnectInvalidHandlerHost() throws Throwable { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - - final AtomicReference e = new AtomicReference<>(); - final CountDownLatch l = new CountDownLatch(1); - - client.prepareGet("http://null.apache.org:9999/").execute(new AsyncCompletionHandlerAdapter() { - @Override - public void onThrowable(Throwable t) { - e.set(t); - l.countDown(); - } - }); - - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timed out"); - } - - assertNotNull(e.get()); - throw e.get(); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncConnectInvalidFuturePort() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncContentLenghtGETTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncResponseEmptyBody() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - Response response = client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerAdapter() { - - @Override - public void onThrowable(Throwable t) { - fail("Unexpected exception", t); - } - }).get(); - - assertEquals(response.getResponseBody(), ""); - } - } - - @Test(groups = { "standalone", "default_provider", "asyncAPI" }) - public void asyncAPIContentLenghtGETTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - // 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "asyncAPI" }) - public void asyncAPIHandlerExceptionTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - // 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoGetDelayHandlerTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(5 * 1000).build())) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoGetQueryStringTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - // 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoGetKeepAliveHandlerTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - // 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"); - } - } - } - - @Test(groups = { "online", "default_provider", "async" }) - public void asyncDoGetMaxRedirectTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(new Builder().setMaxRedirects(0).setFollowRedirect(true).build())) { - // 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"); - } - } - } - - @Test(groups = { "online", "default_provider", "async" }) - public void asyncDoGetNestedTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - // 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"); - } - } - } - - @Test(groups = { "online", "default_provider", "async" }) - public void asyncDoGetStreamAndBodyTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - Response response = client.prepareGet("http://www.lemonde.fr/").execute().get(); - assertEquals(response.getStatusCode(), 200); - } - } - - @Test(groups = { "online", "default_provider", "async" }) - public void asyncUrlWithoutPathTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - Response response = client.prepareGet("http://www.lemonde.fr/").execute().get(); - assertEquals(response.getStatusCode(), 200); - } - } - - @Test(groups = { "default_provider", "async" }) - public void optionsTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - Response response = client.prepareOptions(getTargetUrl()).execute().get(); - - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("Allow"), "GET,HEAD,POST,OPTIONS,TRACE"); - } - } - - @Test(groups = { "online", "default_provider" }) - public void testAwsS3() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - } - - @Test(groups = { "online", "default_provider" }) - public void testAsyncHttpProviderConfig() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(new Builder().setAsyncHttpClientProviderConfig(getProviderConfig()).build())) { - 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); - } - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void idleRequestTimeoutTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setPooledConnectionIdleTimeout(5000).setRequestTimeout(10000).build())) { - 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); - } - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void asyncDoPostCancelTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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()); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void getShouldAllowBody() throws IOException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - client.prepareGet(getTargetUrl()).setBody("Boo!").execute(); - } - } - - @Test(groups = { "standalone", "default_provider" }, expectedExceptions = { NullPointerException.class }) - public void invalidUri() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - client.prepareGet(String.format("http:127.0.0.1:%d/foo/test", port1)).build(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncHttpClientConfigBeanTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfigBean().setUserAgent("test"))) { - Response response = client.executeRequest(client.prepareGet(getTargetUrl()).build()).get(); - assertEquals(200, response.getStatusCode()); - } - } - - @Test(groups = { "default_provider", "async" }) - public void bodyAsByteTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - Response response = client.prepareGet(getTargetUrl()).execute().get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getResponseBodyAsBytes(), new byte[] {}); - } - } - - @Test(groups = { "default_provider", "async" }) - public void mirrorByteTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - Response response = client.preparePost(getTargetUrl()).setBody("MIRROR").execute().get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(new String(response.getResponseBodyAsBytes(), UTF_8), "MIRROR"); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }) - public void testNewConnectionEventsFired() throws Exception { - Request request = new RequestBuilder("GET").setUrl("http://127.0.0.1/" + port1 + "/Test").build(); - - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - EventCollectingHandler handler = new EventCollectingHandler(); - client.executeRequest(request, handler).get(3, TimeUnit.SECONDS); - handler.waitForCompletion(3, TimeUnit.SECONDS); - - List expectedEvents = Arrays.asList( - "ConnectionPool", - "ConnectionOpen", - "DnsResolved", - "ConnectionOpened", - "RequestSend", - "HeadersWritten", - "StatusReceived", - "HeadersReceived", - "ConnectionOffer", - "Completed"); - - assertEquals(handler.firedEvents, expectedEvents, "Got " + Arrays.toString(handler.firedEvents.toArray())); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java b/api/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java deleted file mode 100644 index 23df3502e3..0000000000 --- a/api/src/test/java/org/asynchttpclient/AsyncStreamHandlerTest.java +++ /dev/null @@ -1,475 +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.test.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.AsyncHttpClient; -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); - final AtomicReference responseHeaders = new AtomicReference<>(); - final AtomicReference throwable = new AtomicReference<>(); - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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"); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncStreamPOSTTest() throws Exception { - - final AtomicReference responseHeaders = new AtomicReference<>(); - - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncStreamInterruptTest() throws Exception { - final CountDownLatch l = new CountDownLatch(1); - - final AtomicReference responseHeaders = new AtomicReference<>(); - final AtomicBoolean bodyReceived = new AtomicBoolean(false); - final AtomicReference throwable = new AtomicReference<>(); - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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"); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncStreamFutureTest() throws Exception { - final AtomicReference responseHeaders = new AtomicReference<>(); - final AtomicReference throwable = new AtomicReference<>(); - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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"); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncStreamThrowableRefusedTest() throws Exception { - - final CountDownLatch l = new CountDownLatch(1); - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncStreamReusePOSTTest() throws Exception { - - final AtomicReference responseHeaders = new AtomicReference<>(); - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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"); - } - } - - @Test(groups = { "online", "default_provider" }) - public void asyncStream302RedirectWithBody() throws Exception { - final AtomicReference statusCode = new AtomicReference<>(0); - final AtomicReference responseHeaders = new AtomicReference<>(); - try (AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build())) { - 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"); - } - } - - @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); - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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."); - } - } - } - - @Test(groups = { "online", "default_provider" }) - public void asyncOptionsTest() throws Exception { - final AtomicReference responseHeaders = new AtomicReference<>(); - - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void closeConnectionTest() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/AsyncStreamLifecycleTest.java b/api/src/test/java/org/asynchttpclient/AsyncStreamLifecycleTest.java deleted file mode 100644 index a1658db110..0000000000 --- a/api/src/test/java/org/asynchttpclient/AsyncStreamLifecycleTest.java +++ /dev/null @@ -1,154 +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.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -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 { - try (AsyncHttpClient ahc = getAsyncHttpClient(null)) { - 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); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/AuthTimeoutTest.java b/api/src/test/java/org/asynchttpclient/AuthTimeoutTest.java deleted file mode 100644 index 8f38e32660..0000000000 --- a/api/src/test/java/org/asynchttpclient/AuthTimeoutTest.java +++ /dev/null @@ -1,209 +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 java.nio.charset.StandardCharsets.*; -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.findFreePort; -import static org.asynchttpclient.test.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.Realm; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -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, configureHandler()); - server.start(); - - server2 = newJettyHttpServer(port2); - addDigestAuthHandler(server2, 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(UTF_8).length)); - out.write(content.substring(1).getBytes(UTF_8)); - } else { - response.setStatus(200); - } - out.flush(); - out.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void basicAuthTimeoutTest() throws Exception { - try (AsyncHttpClient client = newClient()) { - Future f = execute(client, server, false); - f.get(); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void basicPreemptiveAuthTimeoutTest() throws Exception { - try (AsyncHttpClient client = newClient()) { - Future f = execute(client, server, true); - f.get(); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void digestAuthTimeoutTest() throws Exception { - try (AsyncHttpClient client = newClient()) { - Future f = execute(client, server2, false); - f.get(); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void digestPreemptiveAuthTimeoutTest() throws Exception { - try (AsyncHttpClient client = newClient()) { - Future f = execute(client, server2, true); - f.get(); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void basicFutureAuthTimeoutTest() throws Exception { - try (AsyncHttpClient client = newClient()) { - Future f = execute(client, server, false); - f.get(1, TimeUnit.SECONDS); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void basicFuturePreemptiveAuthTimeoutTest() throws Exception { - try (AsyncHttpClient client = newClient()) { - Future f = execute(client, server, true); - f.get(1, TimeUnit.SECONDS); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void digestFutureAuthTimeoutTest() throws Exception { - try (AsyncHttpClient client = newClient()) { - Future f = execute(client, server2, false); - f.get(1, TimeUnit.SECONDS); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void digestFuturePreemptiveAuthTimeoutTest() throws Exception { - try (AsyncHttpClient client = newClient()) { - Future f = execute(client, server2, true); - f.get(1, TimeUnit.SECONDS); - fail("expected timeout"); - } catch (Exception e) { - inspectException(e); - } - } - - 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).setConnectTimeout(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/BasicAuthTest.java b/api/src/test/java/org/asynchttpclient/BasicAuthTest.java deleted file mode 100644 index 810c2c43b0..0000000000 --- a/api/src/test/java/org/asynchttpclient/BasicAuthTest.java +++ /dev/null @@ -1,363 +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 java.nio.charset.StandardCharsets.*; -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.addDigestAuthHandler; -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.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.Realm.AuthScheme; -import org.asynchttpclient.request.body.generator.InputStreamBodyGenerator; -import org.asynchttpclient.simple.SimpleAsyncHttpClient; -import org.asynchttpclient.simple.consumer.AppendableBodyConsumer; -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; - - @BeforeClass(alwaysRun = true) - @Override - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - port2 = findFreePort(); - portNoAuth = findFreePort(); - - server = newJettyHttpServer(port1); - addBasicAuthHandler(server, configureHandler()); - server.start(); - - server2 = newJettyHttpServer(port2); - addDigestAuthHandler(server2, 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.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(); - } - } - - 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.setContentLength(0); - - } else { - 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]; - int contentLength = 0; - if (bytes.length > 0) { - int read = request.getInputStream().read(bytes); - if (read > 0) { - contentLength = read; - response.getOutputStream().write(bytes, 0, read); - } - } - response.setContentLength(contentLength); - } - response.getOutputStream().flush(); - response.getOutputStream().close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthTest() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void redirectAndBasicAuthTest() throws Exception, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).setMaxRedirects(10).build())) { - 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")); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basic401Test() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthTestPreemtiveTest() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - // 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthNegativeTest() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthInputStreamTest() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthFileTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthAsyncConfigTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRealm((new Realm.RealmBuilder()).setPrincipal(USER).setPassword(ADMIN).build()).build())) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicAuthFileNoKeepAliveTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(false).build())) { - - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void stringBuilderBodyConsumerTest() throws Exception { - - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setRealmPrincipal(USER).setRealmPassword(ADMIN) - .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)); - - Response response = future.get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(s.toString(), MY_MESSAGE); - assertEquals(response.getStatusCode(), HttpServletResponse.SC_OK); - assertNotNull(response.getHeader("X-Auth")); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void noneAuthTest() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/BasicHttpsTest.java b/api/src/test/java/org/asynchttpclient/BasicHttpsTest.java deleted file mode 100644 index 770157f640..0000000000 --- a/api/src/test/java/org/asynchttpclient/BasicHttpsTest.java +++ /dev/null @@ -1,144 +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.test.TestUtils.SIMPLE_TEXT_FILE; -import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE_STRING; -import static org.asynchttpclient.test.TestUtils.createSSLContext; -import static org.testng.Assert.*; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig.Builder; -import org.asynchttpclient.test.EventCollectingHandler; -import org.testng.annotations.Test; - -import javax.servlet.http.HttpServletResponse; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -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 { - - try (AsyncHttpClient client = getAsyncHttpClient(new Builder().setSSLContext(createSSLContext(new AtomicBoolean(true))).build())) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void multipleSSLRequestsTest() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(new Builder().setSSLContext(createSSLContext(new AtomicBoolean(true))).build())) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void multipleSSLWithoutCacheTest() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(new Builder().setSSLContext(createSSLContext(new AtomicBoolean(true))).setAllowPoolingSslConnections(false).build())) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void reconnectsAfterFailedCertificationPath() throws Exception { - - AtomicBoolean trust = new AtomicBoolean(false); - try (AsyncHttpClient client = getAsyncHttpClient(new Builder().setSSLContext(createSSLContext(trust)).build())) { - 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, TimeUnit.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, TimeUnit.SECONDS); - - assertEquals(response.getResponseBody(), body); - } - } - - @Test(timeOut = 2000, expectedExceptions = { Exception.class } ) - public void failInstantlyIfNotAllowedSelfSignedCertificate() throws Throwable { - - try (AsyncHttpClient client = getAsyncHttpClient(new Builder().setRequestTimeout(2000).build())) { - try { - client.prepareGet(getTargetUrl()).execute().get(TIMEOUT, TimeUnit.SECONDS); - } catch (ExecutionException e) { - throw e.getCause() != null ? e.getCause() : e; - } - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testNormalEventsFired() throws InterruptedException, TimeoutException, ExecutionException { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setSSLContext(createSSLContext(new AtomicBoolean(true))).build())) { - EventCollectingHandler handler = new EventCollectingHandler(); - client.preparePost(getTargetUrl()).setBody("whatever").execute(handler).get(3, TimeUnit.SECONDS); - handler.waitForCompletion(3, TimeUnit.SECONDS); - - List expectedEvents = Arrays.asList( - "ConnectionPool", - "ConnectionOpen", - "DnsResolved", - "SslHandshakeCompleted", - "ConnectionOpened", - "RequestSend", - "HeadersWritten", - "StatusReceived", - "HeadersReceived", - "ConnectionOffer", - "Completed"); - - assertEquals(handler.firedEvents, expectedEvents, "Got " + Arrays.toString(handler.firedEvents.toArray())); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/ByteBufferCapacityTest.java b/api/src/test/java/org/asynchttpclient/ByteBufferCapacityTest.java deleted file mode 100644 index 0bf29de2ba..0000000000 --- a/api/src/test/java/org/asynchttpclient/ByteBufferCapacityTest.java +++ /dev/null @@ -1,104 +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.test.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.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 { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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"); - } - } - } - - public String getTargetUrl() { - return String.format("http://127.0.0.1:%d/foo/test", port1); - } -} diff --git a/api/src/test/java/org/asynchttpclient/ComplexClientTest.java b/api/src/test/java/org/asynchttpclient/ComplexClientTest.java deleted file mode 100644 index adf55bee2d..0000000000 --- a/api/src/test/java/org/asynchttpclient/ComplexClientTest.java +++ /dev/null @@ -1,52 +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.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -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 { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void urlWithoutSlashTest() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/DigestAuthTest.java b/api/src/test/java/org/asynchttpclient/DigestAuthTest.java deleted file mode 100644 index 9952c6d824..0000000000 --- a/api/src/test/java/org/asynchttpclient/DigestAuthTest.java +++ /dev/null @@ -1,105 +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.test.TestUtils.ADMIN; -import static org.asynchttpclient.test.TestUtils.USER; -import static org.asynchttpclient.test.TestUtils.addDigestAuthHandler; -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.Realm; -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, 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 { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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")); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void digestAuthTestWithoutScheme() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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")); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void digestAuthNegativeTest() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/ErrorResponseTest.java b/api/src/test/java/org/asynchttpclient/ErrorResponseTest.java deleted file mode 100644 index 3a3120f92b..0000000000 --- a/api/src/test/java/org/asynchttpclient/ErrorResponseTest.java +++ /dev/null @@ -1,74 +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 java.nio.charset.StandardCharsets.*; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -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(UTF_8)); - out.flush(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new ErrorHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void testQueryParameters() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/Expect100ContinueTest.java b/api/src/test/java/org/asynchttpclient/Expect100ContinueTest.java deleted file mode 100644 index 3c0cb0ade0..0000000000 --- a/api/src/test/java/org/asynchttpclient/Expect100ContinueTest.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; - -import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE; -import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE_STRING; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -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 { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/FluentCaseInsensitiveStringsMapTest.java b/api/src/test/java/org/asynchttpclient/FluentCaseInsensitiveStringsMapTest.java deleted file mode 100644 index 710c2e4016..0000000000 --- a/api/src/test/java/org/asynchttpclient/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; - -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", ", ")); - assertTrue(map.get("baz").isEmpty()); - } - - @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", ", ")); - assertTrue(map.get("baz").isEmpty()); - } - - @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", ", ")); - assertTrue(map.get("foo").isEmpty()); - assertNull(map.getFirstValue("baz")); - assertNull(map.getJoinedValue("baz", ", ")); - assertTrue(map.get("baz").isEmpty()); - } - - @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.replaceWith("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.replaceWith("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.replaceWith(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.replaceWith("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", ", ")); - assertTrue(map.get("baz").isEmpty()); - } - - @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", ", ")); - assertTrue(map.get("foo").isEmpty()); - 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/FluentStringsMapTest.java b/api/src/test/java/org/asynchttpclient/FluentStringsMapTest.java deleted file mode 100644 index 9d9797d3e9..0000000000 --- a/api/src/test/java/org/asynchttpclient/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; - -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.replaceWith("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.replaceWith("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.replaceWith("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.replaceWith("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.replaceWith(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.replaceWith("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/FollowingThreadTest.java b/api/src/test/java/org/asynchttpclient/FollowingThreadTest.java deleted file mode 100644 index 774ab9fe03..0000000000 --- a/api/src/test/java/org/asynchttpclient/FollowingThreadTest.java +++ /dev/null @@ -1,90 +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.AsyncHttpClient; -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); - try (AsyncHttpClient ahc = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build())) { - 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 { - countDown.countDown(); - } - } - }); - } - countDown.await(); - } finally { - pool.shutdown(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/Head302Test.java b/api/src/test/java/org/asynchttpclient/Head302Test.java deleted file mode 100644 index c0eb0697f0..0000000000 --- a/api/src/test/java/org/asynchttpclient/Head302Test.java +++ /dev/null @@ -1,84 +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.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -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 { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/HttpToHttpsRedirectTest.java b/api/src/test/java/org/asynchttpclient/HttpToHttpsRedirectTest.java deleted file mode 100644 index ff1b240cc3..0000000000 --- a/api/src/test/java/org/asynchttpclient/HttpToHttpsRedirectTest.java +++ /dev/null @@ -1,150 +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.test.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; -import static org.asynchttpclient.test.TestUtils.addHttpsConnector; -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -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 { - - // FIXME super NOT threadsafe!!! - 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(); - try (AsyncHttpClient c = getAsyncHttpClient(cg)) { - Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", getTargetUrl2()).execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("X-httpToHttps"), "PASS"); - } - } - - // @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(); - try (AsyncHttpClient c = getAsyncHttpClient(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"); - } - } - - // @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(); - try (AsyncHttpClient c = getAsyncHttpClient(cg)) { - Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", "/foo/test").execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getUri().toString(), getTargetUrl()); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/IdleStateHandlerTest.java b/api/src/test/java/org/asynchttpclient/IdleStateHandlerTest.java deleted file mode 100644 index 7d1283a9ed..0000000000 --- a/api/src/test/java/org/asynchttpclient/IdleStateHandlerTest.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; - -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.newJettyHttpServer; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -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(); - - try (AsyncHttpClient c = getAsyncHttpClient(cg)) { - c.prepareGet(getTargetUrl()).execute().get(); - } catch (ExecutionException e) { - fail("Should allow to finish processing request.", e); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/ListenableFutureTest.java b/api/src/test/java/org/asynchttpclient/ListenableFutureTest.java deleted file mode 100644 index 540438ad20..0000000000 --- a/api/src/test/java/org/asynchttpclient/ListenableFutureTest.java +++ /dev/null @@ -1,54 +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.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); - try (AsyncHttpClient ahc = getAsyncHttpClient(null)) { - 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); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/MultipleHeaderTest.java b/api/src/test/java/org/asynchttpclient/MultipleHeaderTest.java deleted file mode 100644 index d4cf2b2f14..0000000000 --- a/api/src/test/java/org/asynchttpclient/MultipleHeaderTest.java +++ /dev/null @@ -1,194 +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.test.TestUtils.findFreePort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -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 }; - - try (AsyncHttpClient ahc = getAsyncHttpClient(null)) { - 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"); - } - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testMultipleEntityHeaders() throws IOException, ExecutionException, TimeoutException, InterruptedException { - final String[] clHeaders = new String[] { null, null }; - - try (AsyncHttpClient ahc = getAsyncHttpClient(null)) { - 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"); - } - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/NoNullResponseTest.java b/api/src/test/java/org/asynchttpclient/NoNullResponseTest.java deleted file mode 100644 index de59d67550..0000000000 --- a/api/src/test/java/org/asynchttpclient/NoNullResponseTest.java +++ /dev/null @@ -1,75 +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.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -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 { - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).setSSLContext(getSSLContext()).setAllowPoolingConnections(true).setConnectTimeout(10000) - .setPooledConnectionIdleTimeout(60000).setRequestTimeout(10000).setMaxConnectionsPerHost(-1).setMaxConnections(-1).build(); - - try (AsyncHttpClient client = getAsyncHttpClient(config)) { - 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); - } - } - - 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/NonAsciiContentLengthTest.java b/api/src/test/java/org/asynchttpclient/NonAsciiContentLengthTest.java deleted file mode 100644 index 84584059b1..0000000000 --- a/api/src/test/java/org/asynchttpclient/NonAsciiContentLengthTest.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; - -import static java.nio.charset.StandardCharsets.*; -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -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; - 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(); - } - - @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 { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - BoundRequestBuilder r = client.preparePost(getTargetUrl()).setBody(body).setBodyCharset(UTF_8); - Future f = r.execute(); - Response resp = f.get(); - assertEquals(resp.getStatusCode(), 200); - assertEquals(body, resp.getResponseBody(UTF_8)); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/ParamEncodingTest.java b/api/src/test/java/org/asynchttpclient/ParamEncodingTest.java deleted file mode 100644 index e5b31f8b78..0000000000 --- a/api/src/test/java/org/asynchttpclient/ParamEncodingTest.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; - -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`~!@#$%^&*()_+-=,.<>/?;:'\"[]{}\\| "; - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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()); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new ParamEncoding(); - } -} diff --git a/api/src/test/java/org/asynchttpclient/PerRequestRelative302Test.java b/api/src/test/java/org/asynchttpclient/PerRequestRelative302Test.java deleted file mode 100644 index 500389d065..0000000000 --- a/api/src/test/java/org/asynchttpclient/PerRequestRelative302Test.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; - -import static org.asynchttpclient.test.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.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.uri.Uri; -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 { - - // FIXME super NOT threadsafe!!! - 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); - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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); - } - } - - // @Test(groups = { "online", "default_provider" }) - public void notRedirected302Test() throws Exception { - isSet.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); - try (AsyncHttpClient c = getAsyncHttpClient(cg)) { - Response response = c.prepareGet(getTargetUrl()).setFollowRedirect(false).setHeader("X-redirect", "http://www.microsoft.com/").execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 302); - } - } - - private 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 int getPort(Uri 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); - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - // 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); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void relativeLocationUrl() throws Exception { - isSet.getAndSet(false); - - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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()); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java b/api/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java deleted file mode 100644 index e8e8c2add5..0000000000 --- a/api/src/test/java/org/asynchttpclient/PerRequestTimeoutTest.java +++ /dev/null @@ -1,172 +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.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.AsyncHttpClient; -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 { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - Future responseFuture = client.prepareGet(getTargetUrl()).setRequestTimeout(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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testGlobalDefaultPerRequestInfiniteTimeout() throws IOException { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(100).build())) { - Future responseFuture = client.prepareGet(getTargetUrl()).setRequestTimeout(-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()); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testGlobalRequestTimeout() throws IOException { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(100).build())) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testGlobalIdleTimeout() throws IOException { - final long times[] = new long[] { -1, -1 }; - - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setPooledConnectionIdleTimeout(2000).build())) { - 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); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/PostRedirectGetTest.java b/api/src/test/java/org/asynchttpclient/PostRedirectGetTest.java deleted file mode 100644 index 9905259126..0000000000 --- a/api/src/test/java/org/asynchttpclient/PostRedirectGetTest.java +++ /dev/null @@ -1,204 +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; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AsyncHttpClient; -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" }, enabled = false) - public void postRedirectGet302Test() throws Exception { - doTestPositive(302); - } - - @Test(groups = { "standalone", "post_redirect_get" }, enabled = false) - public void postRedirectGet302StrictTest() throws Exception { - doTestNegative(302, true); - } - - @Test(groups = { "standalone", "post_redirect_get" }, enabled = false) - public void postRedirectGet303Test() throws Exception { - doTestPositive(303); - } - - @Test(groups = { "standalone", "post_redirect_get" }, enabled = false) - public void postRedirectGet301Test() throws Exception { - doTestNegative(301, false); - } - - @Test(groups = { "standalone", "post_redirect_get" }, enabled = false) - public void postRedirectGet307Test() throws Exception { - doTestNegative(307, false); - } - - // --------------------------------------------------------- Private Methods - - private void doTestNegative(final int status, boolean strict) throws Exception { - - AsyncHttpClientConfig config = 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 (AsyncHttpClient p = getAsyncHttpClient(config)) { - 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); - } - } - - private void doTestPositive(final int status) throws Exception { - - AsyncHttpClientConfig config = 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 (AsyncHttpClient p = getAsyncHttpClient(config)) { - 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); - } - } - - // ---------------------------------------------------------- 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/PostWithQSTest.java b/api/src/test/java/org/asynchttpclient/PostWithQSTest.java deleted file mode 100644 index 82d8d41f18..0000000000 --- a/api/src/test/java/org/asynchttpclient/PostWithQSTest.java +++ /dev/null @@ -1,145 +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 static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -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 { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void postWithNulParamQS() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void postWithNulParamsQS() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void postWithEmptyParamsQS() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new PostWithQSHandler(); - } -} diff --git a/api/src/test/java/org/asynchttpclient/QueryParametersTest.java b/api/src/test/java/org/asynchttpclient/QueryParametersTest.java deleted file mode 100644 index 7b86302ae9..0000000000 --- a/api/src/test/java/org/asynchttpclient/QueryParametersTest.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; - -import static java.nio.charset.StandardCharsets.*; -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.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 { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testUrlRequestParametersEncoding() throws IOException, ExecutionException, InterruptedException { - String URL = getTargetUrl() + "?q="; - String REQUEST_PARAM = "github github \ngithub"; - - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - String requestUrl2 = URL + URLEncoder.encode(REQUEST_PARAM, 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"), UTF_8.name()); - assertEquals(s, REQUEST_PARAM); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void urlWithColonTest() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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"), query); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/RC10KTest.java b/api/src/test/java/org/asynchttpclient/RC10KTest.java deleted file mode 100644 index 96c6a648dc..0000000000 --- a/api/src/test/java/org/asynchttpclient/RC10KTest.java +++ /dev/null @@ -1,144 +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.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -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 { - try (AsyncHttpClient ahc = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setMaxConnectionsPerHost(C10K).setAllowPoolingConnections(true).build())) { - 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++); - } - } - } - - 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/RealmTest.java b/api/src/test/java/org/asynchttpclient/RealmTest.java deleted file mode 100644 index 26c40e8c83..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 java.nio.charset.StandardCharsets.*; -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.Realm.AuthScheme; -import org.asynchttpclient.Realm.RealmBuilder; -import org.asynchttpclient.uri.Uri; -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.setCharset(UTF_16).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.getCharset(), orig.getCharset()); - assertEquals(clone.getUsePreemptiveAuth(), orig.getUsePreemptiveAuth()); - assertEquals(clone.getRealmName(), orig.getRealmName()); - assertEquals(clone.getAlgorithm(), orig.getAlgorithm()); - assertEquals(clone.getScheme(), orig.getScheme()); - } - - @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"; - Uri uri = Uri.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.getPath()); - 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"; - Uri uri = Uri.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.getPath()); - 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/RedirectConnectionUsageTest.java b/api/src/test/java/org/asynchttpclient/RedirectConnectionUsageTest.java deleted file mode 100644 index fed3418b41..0000000000 --- a/api/src/test/java/org/asynchttpclient/RedirectConnectionUsageTest.java +++ /dev/null @@ -1,122 +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.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AsyncHttpClient; -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)// - .setConnectTimeout(1000)// - .setRequestTimeout(1000)// - .setFollowRedirect(true)// - .build(); - - try (AsyncHttpClient c = getAsyncHttpClient(config)) { - 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"); - } - } - - @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/Relative302Test.java b/api/src/test/java/org/asynchttpclient/Relative302Test.java deleted file mode 100644 index 2b5bd03f64..0000000000 --- a/api/src/test/java/org/asynchttpclient/Relative302Test.java +++ /dev/null @@ -1,170 +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.test.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.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.uri.Uri; -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.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(); - } - } - - @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(); - - try (AsyncHttpClient c = getAsyncHttpClient(cg)) { - 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); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void redirected302InvalidTest() throws Exception { - isSet.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); - - // If the test hit a proxy, no ConnectException will be thrown and instead of 404 will be returned. - try (AsyncHttpClient c = getAsyncHttpClient(cg)) { - 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); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void absolutePathRedirectTest() throws Exception { - isSet.getAndSet(false); - - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); - try (AsyncHttpClient c = getAsyncHttpClient(cg)) { - 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); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void relativePathRedirectTest() throws Exception { - isSet.getAndSet(false); - - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); - try (AsyncHttpClient c = getAsyncHttpClient(cg)) { - 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 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 int getPort(Uri 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/RemoteSiteTest.java b/api/src/test/java/org/asynchttpclient/RemoteSiteTest.java deleted file mode 100644 index aac9a3f974..0000000000 --- a/api/src/test/java/org/asynchttpclient/RemoteSiteTest.java +++ /dev/null @@ -1,231 +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 java.nio.charset.StandardCharsets.*; -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.AsyncHttpClient; -import org.asynchttpclient.cookie.Cookie; -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 { - try (AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build())) { - Response response = c.prepareGet("http://www.google.com/").execute().get(10, TimeUnit.SECONDS); - assertNotNull(response); - } - } - - @Test(groups = { "online", "default_provider" }) - public void testMailGoogleCom() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build())) { - Response response = c.prepareGet("http://mail.google.com/").execute().get(10, TimeUnit.SECONDS); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - } - } - - @Test(groups = { "online", "default_provider" }, enabled = false) - // FIXME - public void testMicrosoftCom() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build())) { - Response response = c.prepareGet("http://microsoft.com/").execute().get(10, TimeUnit.SECONDS); - assertNotNull(response); - assertEquals(response.getStatusCode(), 301); - } - } - - @Test(groups = { "online", "default_provider" }, enabled = false) - // FIXME - public void testWwwMicrosoftCom() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build())) { - Response response = c.prepareGet("http://www.microsoft.com/").execute().get(10, TimeUnit.SECONDS); - assertNotNull(response); - assertEquals(response.getStatusCode(), 302); - } - } - - @Test(groups = { "online", "default_provider" }, enabled = false) - // FIXME - public void testUpdateMicrosoftCom() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build())) { - Response response = c.prepareGet("http://update.microsoft.com/").execute().get(10, TimeUnit.SECONDS); - assertNotNull(response); - assertEquals(response.getStatusCode(), 302); - } - } - - @Test(groups = { "online", "default_provider" }) - public void testGoogleComWithTimeout() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build())) { - Response response = c.prepareGet("http://google.com/").execute().get(10, TimeUnit.SECONDS); - assertNotNull(response); - assertTrue(response.getStatusCode() == 301 || response.getStatusCode() == 302); - } - } - - @Test(groups = { "online", "default_provider" }) - public void asyncStatusHEADContentLenghtTest() throws Exception { - try (AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build())) { - 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"); - } - } - } - - @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(); - - try (AsyncHttpClient c = getAsyncHttpClient(config)) { - 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"); - } - } - - @Test(groups = { "online", "default_provider" }) - public void asyncFullBodyProperlyRead() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - // FIXME Get a 302 in France... - @Test(groups = { "online", "default_provider" }, enabled = false) - public void testUrlRequestParametersEncoding() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - String requestUrl2 = URL + URLEncoder.encode(REQUEST_PARAM, UTF_8.name()); - logger.info(String.format("Executing request [%s] ...", requestUrl2)); - Response response = client.prepareGet(requestUrl2).execute().get(); - assertEquals(response.getStatusCode(), 302); - } - } - - @Test(groups = { "online", "default_provider" }) - public void stripQueryStringTest() throws Exception { - - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); - try (AsyncHttpClient c = getAsyncHttpClient(cg)) { - Response response = c.prepareGet("http://www.freakonomics.com/?p=55846").execute().get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - } - } - - @Test(groups = { "online", "default_provider" }) - public void evilCoookieTest() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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", false, ".google.com", "/", Long.MIN_VALUE, false, false)); - Request request2 = builder2.build(); - Response response = c.executeRequest(request2).get(); - - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - } - } - - @Test(groups = { "online", "default_provider" }, enabled = false) - public void testAHC62Com() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build())) { - 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); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/RequestBuilderTest.java b/api/src/test/java/org/asynchttpclient/RequestBuilderTest.java deleted file mode 100644 index bc2c64a876..0000000000 --- a/api/src/test/java/org/asynchttpclient/RequestBuilderTest.java +++ /dev/null @@ -1,125 +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 java.nio.charset.StandardCharsets.UTF_8; -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.getUrl(), "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.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")); - } - - @Test(groups = {"standalone", "default_provider"}) - public void testUserProvidedRequestMethod() { - Request req = new RequestBuilder("ABC").setUrl("http://foo.com/").build(); - assertEquals(req.getMethod(), "ABC"); - assertEquals(req.getUrl(), "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.getUrl(), "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.getBodyCharset(), UTF_8); - final Request req2 = new RequestBuilder("GET").setHeader("Content-Type", "application/json; charset=\"utf-8\"").build(); - assertEquals(req2.getBodyCharset(), UTF_8); - } -} diff --git a/api/src/test/java/org/asynchttpclient/RetryRequestTest.java b/api/src/test/java/org/asynchttpclient/RetryRequestTest.java deleted file mode 100644 index e68b9b6a93..0000000000 --- a/api/src/test/java/org/asynchttpclient/RetryRequestTest.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; - -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.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 { - try (AsyncHttpClient ahc = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setMaxRequestRetry(0).build())) { - 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(); - } - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/channel/MaxConnectionsInThreads.java b/api/src/test/java/org/asynchttpclient/channel/MaxConnectionsInThreads.java deleted file mode 100644 index 73c42fc85b..0000000000 --- a/api/src/test/java/org/asynchttpclient/channel/MaxConnectionsInThreads.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.channel; - -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; - -import java.io.IOException; -import java.io.OutputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicInteger; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.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.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; - -abstract public class MaxConnectionsInThreads extends AbstractBasicTest { - - // FIXME weird - private static URI servletEndpointUri; - - @Test(groups = { "online", "default_provider" }) - public void testMaxConnectionsWithinThreads() throws InterruptedException { - - String[] urls = new String[] { servletEndpointUri.toString(), servletEndpointUri.toString() }; - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setConnectTimeout(1000).setRequestTimeout(5000).setAllowPoolingConnections(true)// - .setMaxConnections(1).setMaxConnectionsPerHost(1).build(); - - final CountDownLatch inThreadsLatch = new CountDownLatch(2); - final AtomicInteger failedCount = new AtomicInteger(); - - try (AsyncHttpClient client = getAsyncHttpClient(config)) { - for (int i = 0; i < urls.length; i++) { - final String url = urls[i]; - Thread t = new Thread() { - 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 (int i = 0; i < urls.length; i++) { - final String url = urls[i]; - 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 - @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/channel/MaxTotalConnectionTest.java b/api/src/test/java/org/asynchttpclient/channel/MaxTotalConnectionTest.java deleted file mode 100644 index 53eba836fb..0000000000 --- a/api/src/test/java/org/asynchttpclient/channel/MaxTotalConnectionTest.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.channel; - -import static org.testng.Assert.*; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; - -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 org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testng.Assert; -import org.testng.annotations.Test; - -public abstract class MaxTotalConnectionTest extends AbstractBasicTest { - protected final Logger log = LoggerFactory.getLogger(AbstractBasicTest.class); - - @Test(groups = { "standalone", "default_provider" }) - public void testMaxTotalConnectionsExceedingException() throws IOException { - String[] urls = new String[] { "http://google.com/", "http://github.com/" }; - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setConnectTimeout(1000) - .setRequestTimeout(5000).setAllowPoolingConnections(false).setMaxConnections(1).setMaxConnectionsPerHost(1) - .build(); - - try (AsyncHttpClient client = getAsyncHttpClient(config)) { - List> futures = new ArrayList<>(); - for (int i = 0; i < urls.length; i++) { - futures.add(client.prepareGet(urls[i]).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; - } - } - - Assert.assertEquals(1, i); - Assert.assertTrue(caughtError); - } - } - - @Test - public void testMaxTotalConnections() throws InterruptedException { - String[] urls = new String[] { "http://google.com/", "http://lenta.ru/" }; - - final CountDownLatch latch = new CountDownLatch(2); - final AtomicReference ex = new AtomicReference<>(); - final AtomicReference failedUrl = new AtomicReference<>(); - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setConnectTimeout(1000).setRequestTimeout(5000) - .setAllowPoolingConnections(false).setMaxConnections(2).setMaxConnectionsPerHost(1).build(); - - try (AsyncHttpClient client = getAsyncHttpClient(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/api/src/test/java/org/asynchttpclient/channel/pool/ConnectionPoolTest.java b/api/src/test/java/org/asynchttpclient/channel/pool/ConnectionPoolTest.java deleted file mode 100644 index 8ccfc017d8..0000000000 --- a/api/src/test/java/org/asynchttpclient/channel/pool/ConnectionPoolTest.java +++ /dev/null @@ -1,301 +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.channel.pool; - -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 java.io.IOException; -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 org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncCompletionHandlerBase; -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.test.EventCollectingHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testng.annotations.Test; - -public abstract class ConnectionPoolTest extends AbstractBasicTest { - protected final Logger log = LoggerFactory.getLogger(AbstractBasicTest.class); - - @Test(groups = { "standalone", "default_provider" }) - public void testMaxTotalConnections() { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setMaxConnections(1).build())) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testMaxTotalConnectionsException() throws IOException { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setMaxConnections(1).build())) { - String url = getTargetUrl(); - - List> futures = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - log.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); - assertNotNull(exception.getCause()); - assertEquals(exception.getCause().getMessage(), "Too many connections 1"); - } - } - - @Test(groups = { "standalone", "default_provider", "async" }, enabled = true, invocationCount = 10, alwaysRun = true) - public void asyncDoGetKeepAliveHandlerTest_channelClosedDoesNotFail() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - // 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void multipleMaxConnectionOpenTest() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setConnectTimeout(5000).setMaxConnections(1).build(); - try (AsyncHttpClient c = getAsyncHttpClient(cg)) { - 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); - assertNotNull(exception.getCause()); - assertEquals(exception.getCause().getMessage(), "Too many connections 1"); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void multipleMaxConnectionOpenTestWithQuery() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setConnectTimeout(5000).setMaxConnections(1).build(); - try (AsyncHttpClient c = getAsyncHttpClient(cg)) { - 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); - } - } - - /** - * 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); - - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void asyncHandlerOnThrowableTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void nonPoolableConnectionReleaseSemaphoresTest() throws Throwable { - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() - .setMaxConnections(6) - .setMaxConnectionsPerHost(3) - .build(); - - Request request = new RequestBuilder().setUrl(getTargetUrl()).setHeader("Connection", "close").build(); - - try (AsyncHttpClient client = getAsyncHttpClient(config)) { - 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(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testPooledEventsFired() throws Exception { - Request request = new RequestBuilder("GET").setUrl("http://127.0.0.1/" + port1 + "/Test").build(); - - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - - List expectedEvents = Arrays.asList( - "ConnectionPool", - "ConnectionPooled", - "RequestSend", - "HeadersWritten", - "StatusReceived", - "HeadersReceived", - "ConnectionOffer", - "Completed"); - - assertEquals(secondHandler.firedEvents, expectedEvents, "Got " + Arrays.toString(secondHandler.firedEvents.toArray())); - } - } -} 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 9c5f735b19..0000000000 --- a/api/src/test/java/org/asynchttpclient/cookie/CookieDecoderTest.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.cookie; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; - -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.isWrap(), false); - 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.isWrap(), true); - } - - @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.isWrap(), true); - } - - @Test(groups = "fast") - public void testIgnoreEmptyDomain() { - Cookie cookie = CookieDecoder.decode("sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;Domain=;Path=/"); - assertNull(cookie.getDomain()); - } -} diff --git a/api/src/test/java/org/asynchttpclient/cookie/RFC2616DateParserTest.java b/api/src/test/java/org/asynchttpclient/cookie/RFC2616DateParserTest.java deleted file mode 100644 index 72dd77069e..0000000000 --- a/api/src/test/java/org/asynchttpclient/cookie/RFC2616DateParserTest.java +++ /dev/null @@ -1,112 +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; - -import java.text.ParseException; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.TimeZone; - -/** - * See http://tools.ietf.org/html/rfc2616#section-3.3 - * - * @author slandelle - */ -public class RFC2616DateParserTest { - - @Test(groups = "fast") - public void testRFC822() throws ParseException { - Date date = RFC2616DateParser.get().parse("Sun, 06 Nov 1994 08:49:37 GMT"); - assertNotNull(date); - - Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT")); - cal.setTime(date); - assertEquals(cal.get(Calendar.DAY_OF_WEEK), Calendar.SUNDAY); - assertEquals(cal.get(Calendar.DAY_OF_MONTH), 6); - assertEquals(cal.get(Calendar.MONTH), Calendar.NOVEMBER); - assertEquals(cal.get(Calendar.YEAR), 1994); - assertEquals(cal.get(Calendar.HOUR), 8); - assertEquals(cal.get(Calendar.MINUTE), 49); - assertEquals(cal.get(Calendar.SECOND), 37); - } - - @Test(groups = "fast") - public void testRFC822SingleDigitDayOfMonth() throws ParseException { - Date date = RFC2616DateParser.get().parse("Sun, 6 Nov 1994 08:49:37 GMT"); - assertNotNull(date); - - Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT")); - cal.setTime(date); - assertEquals(cal.get(Calendar.DAY_OF_WEEK), Calendar.SUNDAY); - assertEquals(cal.get(Calendar.DAY_OF_MONTH), 6); - assertEquals(cal.get(Calendar.MONTH), Calendar.NOVEMBER); - assertEquals(cal.get(Calendar.YEAR), 1994); - assertEquals(cal.get(Calendar.HOUR), 8); - assertEquals(cal.get(Calendar.MINUTE), 49); - assertEquals(cal.get(Calendar.SECOND), 37); - } - - @Test(groups = "fast") - public void testRFC822SingleDigitHour() throws ParseException { - Date date = RFC2616DateParser.get().parse("Sun, 6 Nov 1994 8:49:37 GMT"); - assertNotNull(date); - - Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT")); - cal.setTime(date); - assertEquals(cal.get(Calendar.DAY_OF_WEEK), Calendar.SUNDAY); - assertEquals(cal.get(Calendar.DAY_OF_MONTH), 6); - assertEquals(cal.get(Calendar.MONTH), Calendar.NOVEMBER); - assertEquals(cal.get(Calendar.YEAR), 1994); - assertEquals(cal.get(Calendar.HOUR), 8); - assertEquals(cal.get(Calendar.MINUTE), 49); - assertEquals(cal.get(Calendar.SECOND), 37); - } - - @Test(groups = "fast") - public void testRFC850() throws ParseException { - Date date = RFC2616DateParser.get().parse("Sunday, 06-Nov-94 08:49:37 GMT"); - assertNotNull(date); - - Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT")); - cal.setTime(date); - assertEquals(cal.get(Calendar.DAY_OF_WEEK), Calendar.SUNDAY); - assertEquals(cal.get(Calendar.DAY_OF_MONTH), 6); - assertEquals(cal.get(Calendar.MONTH), Calendar.NOVEMBER); - assertEquals(cal.get(Calendar.YEAR), 1994); - assertEquals(cal.get(Calendar.HOUR), 8); - assertEquals(cal.get(Calendar.MINUTE), 49); - assertEquals(cal.get(Calendar.SECOND), 37); - } - - @Test(groups = "fast") - public void testANSIC() throws ParseException { - Date date = RFC2616DateParser.get().parse("Sun Nov 6 08:49:37 1994"); - assertNotNull(date); - - Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT")); - cal.setTime(date); - assertEquals(cal.get(Calendar.DAY_OF_WEEK), Calendar.SUNDAY); - assertEquals(cal.get(Calendar.DAY_OF_MONTH), 6); - assertEquals(cal.get(Calendar.MONTH), Calendar.NOVEMBER); - assertEquals(cal.get(Calendar.YEAR), 1994); - assertEquals(cal.get(Calendar.HOUR), 8); - assertEquals(cal.get(Calendar.MINUTE), 49); - assertEquals(cal.get(Calendar.SECOND), 37); - } -} diff --git a/api/src/test/java/org/asynchttpclient/filter/FilterTest.java b/api/src/test/java/org/asynchttpclient/filter/FilterTest.java deleted file mode 100644 index 54791c1bc8..0000000000 --- a/api/src/test/java/org/asynchttpclient/filter/FilterTest.java +++ /dev/null @@ -1,211 +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 static org.testng.Assert.*; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.asynchttpclient.AbstractBasicTest; -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; - -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)); - - try (AsyncHttpClient c = getAsyncHttpClient(b.build())) { - Response response = c.preparePost(getTargetUrl()).execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void loadThrottleTest() throws Exception { - AsyncHttpClientConfig.Builder b = new AsyncHttpClientConfig.Builder(); - b.addRequestFilter(new ThrottleRequestFilter(10)); - - try (AsyncHttpClient c = getAsyncHttpClient(b.build())) { - 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); - } - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void maxConnectionsText() throws Exception { - AsyncHttpClientConfig.Builder b = new AsyncHttpClientConfig.Builder(); - b.addRequestFilter(new ThrottleRequestFilter(0, 1000)); - - try (AsyncHttpClient c = getAsyncHttpClient(b.build())) { - c.preparePost(getTargetUrl()).execute().get(); - fail("Should have timed out"); - } catch (ExecutionException ex) { - assertTrue(ex.getCause() instanceof FilterException); - } - } - - @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; - } - - }); - - try (AsyncHttpClient c = getAsyncHttpClient(b.build())) { - Response response = c.preparePost(getTargetUrl()).execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - } - } - - @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; - } - - }); - - try (AsyncHttpClient c = getAsyncHttpClient(b.build())) { - Response response = c.preparePost(getTargetUrl()).execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("X-Replay"), "true"); - } - } - - @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; - } - - }); - - try (AsyncHttpClient c = getAsyncHttpClient(b.build())) { - Response response = c.preparePost(getTargetUrl()).execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("X-Replay"), "true"); - } - } - - @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; - } - - }); - - try (AsyncHttpClient c = getAsyncHttpClient(b.build())) { - Response response = c.preparePost(getTargetUrl()).addHeader("Ping", "Pong").execute().get(); - assertNotNull(response); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("Ping"), "Pong"); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/handler/BodyDeferringAsyncHandlerTest.java b/api/src/test/java/org/asynchttpclient/handler/BodyDeferringAsyncHandlerTest.java deleted file mode 100644 index 5b64a0d7aa..0000000000 --- a/api/src/test/java/org/asynchttpclient/handler/BodyDeferringAsyncHandlerTest.java +++ /dev/null @@ -1,253 +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.handler; - -import static org.apache.commons.io.IOUtils.copy; -import static org.asynchttpclient.test.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.AbstractBasicTest; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.BoundRequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.handler.BodyDeferringAsyncHandler; -import org.asynchttpclient.handler.BodyDeferringAsyncHandler.BodyDeferringInputStream; -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 { - try (AsyncHttpClient client = getAsyncHttpClient(getAsyncHttpClientConfig())) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - public void deferredSimpleWithFailure() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(getAsyncHttpClientConfig())) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void deferredInputStreamTrick() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(getAsyncHttpClientConfig())) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void deferredInputStreamTrickWithFailure() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(getAsyncHttpClientConfig())) { - 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! - } - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testConnectionRefused() throws IOException, ExecutionException, TimeoutException, InterruptedException { - int newPortWithoutAnyoneListening = findFreePort(); - try (AsyncHttpClient client = getAsyncHttpClient(getAsyncHttpClientConfig())) { - 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 - } - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/handler/resumable/MapResumableProcessor.java b/api/src/test/java/org/asynchttpclient/handler/resumable/MapResumableProcessor.java deleted file mode 100644 index 1f43328b5b..0000000000 --- a/api/src/test/java/org/asynchttpclient/handler/resumable/MapResumableProcessor.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.asynchttpclient.handler.resumable; - -import org.asynchttpclient.handler.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/handler/resumable/PropertiesBasedResumableProcesserTest.java b/api/src/test/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcesserTest.java deleted file mode 100644 index bd672f0261..0000000000 --- a/api/src/test/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcesserTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.asynchttpclient.handler.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.asynchttpclient.handler.resumable.PropertiesBasedResumableProcessor; -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/handler/resumable/ResumableAsyncHandlerTest.java b/api/src/test/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandlerTest.java deleted file mode 100644 index 93387259bc..0000000000 --- a/api/src/test/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandlerTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.asynchttpclient.handler.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.asynchttpclient.handler.resumable.ResumableAsyncHandler; -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/ntlm/NtlmTest.java b/api/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java deleted file mode 100644 index c3b0d4db9c..0000000000 --- a/api/src/test/java/org/asynchttpclient/ntlm/NtlmTest.java +++ /dev/null @@ -1,103 +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.ntlm; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.Realm.AuthScheme; -import org.asynchttpclient.Realm.RealmBuilder; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.Assert; -import org.testng.annotations.Test; - -public abstract class NtlmTest extends AbstractBasicTest { - - public static class NTLMHandler extends AbstractHandler { - - @Override - public void handle(String pathInContext, org.eclipse.jetty.server.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 (authorization.equals("NTLM TlRMTVNTUAABAAAAAYIIogAAAAAoAAAAAAAAACgAAAAFASgKAAAADw==")) { - httpResponse.setStatus(401); - httpResponse.setHeader("WWW-Authenticate", "NTLM TlRMTVNTUAACAAAAAAAAACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAA=="); - - } else if (authorization - .equals("NTLM TlRMTVNTUAADAAAAGAAYAEgAAAAYABgAYAAAABQAFAB4AAAADAAMAIwAAAASABIAmAAAAAAAAACqAAAAAYIAAgUBKAoAAAAPrYfKbe/jRoW5xDxHeoxC1gBmfWiS5+iX4OAN4xBKG/IFPwfH3agtPEia6YnhsADTVQBSAFMAQQAtAE0ASQBOAE8AUgBaAGEAcABoAG8AZABMAGkAZwBoAHQAQwBpAHQAeQA=")) { - httpResponse.setStatus(200); - } else { - httpResponse.setStatus(401); - } - - httpResponse.setContentLength(0); - httpResponse.getOutputStream().flush(); - httpResponse.getOutputStream().close(); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new NTLMHandler(); - } - - private RealmBuilder realmBuilderBase() { - return new Realm.RealmBuilder()// - .setScheme(AuthScheme.NTLM)// - .setNtlmDomain("Ursa-Minor")// - .setNtlmHost("LightCity")// - .setPrincipal("Zaphod")// - .setPassword("Beeblebrox"); - } - - private void ntlmAuthTest(RealmBuilder realmBuilder) throws IOException, InterruptedException, ExecutionException { - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setRealm(realmBuilder.build()).build(); - - try (AsyncHttpClient client = getAsyncHttpClient(config)) { - Request request = new RequestBuilder("GET").setUrl(getTargetUrl()).build(); - Future responseFuture = client.executeRequest(request); - int status = responseFuture.get().getStatusCode(); - Assert.assertEquals(status, 200); - } - } - - @Test - public void lazyNTLMAuthTest() throws IOException, InterruptedException, ExecutionException { - ntlmAuthTest(realmBuilderBase()); - } - - @Test - public void preemptiveNTLMAuthTest() throws IOException, InterruptedException, ExecutionException { - ntlmAuthTest(realmBuilderBase().setUsePreemptiveAuth(true)); - } -} - diff --git a/api/src/test/java/org/asynchttpclient/oauth/OAuthSignatureCalculatorTest.java b/api/src/test/java/org/asynchttpclient/oauth/OAuthSignatureCalculatorTest.java deleted file mode 100644 index b9c1c78a1d..0000000000 --- a/api/src/test/java/org/asynchttpclient/oauth/OAuthSignatureCalculatorTest.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.oauth; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.asynchttpclient.Param; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.uri.Uri; -import org.testng.annotations.Test; - -/** - * Tests the OAuth signature behavior. - * - * See Signature Tester for an online oauth signature checker. - * - */ -public class OAuthSignatureCalculatorTest { - 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; - - private static class StaticOAuthSignatureCalculator extends OAuthSignatureCalculator { - - private final long timestamp; - private final String nonce; - - public StaticOAuthSignatureCalculator(ConsumerKey consumerAuth, RequestToken userAuth, long timestamp, String nonce) { - super(consumerAuth, userAuth); - this.timestamp = timestamp; - this.nonce = nonce; - } - - @Override - protected long generateTimestamp() { - return timestamp; - } - - @Override - protected String generateNonce() { - return nonce; - } - } - - // sample from RFC https://tools.ietf.org/html/rfc5849#section-3.4.1 - private void testSignatureBaseString(Request request) { - ConsumerKey consumer = new ConsumerKey("9djdj82h48djs9d2", CONSUMER_SECRET); - RequestToken user = new RequestToken("kkk9d7dh3k39sjv7", TOKEN_SECRET); - OAuthSignatureCalculator calc = new OAuthSignatureCalculator(consumer, user); - - String signatureBaseString = calc.signatureBaseString(// - request.getMethod(),// - request.getUri(),// - 137131201,// - "7d8f3e4a",// - request.getFormParams(),// - request.getQueryParams()).toString(); - - assertEquals(signatureBaseString, "POST&" // - + "http%3A%2F%2Fexample.com%2Frequest" // - + "&a2%3Dr%2520b%26"// - + "a3%3D2%2520q%26" + "a3%3Da%26"// - + "b5%3D%253D%25253D%26"// - + "c%2540%3D%26"// - + "c2%3D%26"// - + "oauth_consumer_key%3D9djdj82h48djs9d2%26"// - + "oauth_nonce%3D7d8f3e4a%26"// - + "oauth_signature_method%3DHMAC-SHA1%26"// - + "oauth_timestamp%3D137131201%26"// - + "oauth_token%3Dkkk9d7dh3k39sjv7%26"// - + "oauth_version%3D1.0"); - } - - // fork above test with an OAuth token that requires encoding - private void testSignatureBaseStringWithEncodableOAuthToken(Request request) { - ConsumerKey consumer = new ConsumerKey("9djdj82h48djs9d2", CONSUMER_SECRET); - RequestToken user = new RequestToken("kkk9d7dh3k39sjv7", TOKEN_SECRET); - OAuthSignatureCalculator calc = new OAuthSignatureCalculator(consumer, user); - - String signatureBaseString = calc.signatureBaseString(// - request.getMethod(),// - request.getUri(),// - 137131201,// - "ZLc92RAkooZcIO/0cctl0Q==",// - request.getFormParams(),// - request.getQueryParams()).toString(); - - assertEquals(signatureBaseString, "POST&" // - + "http%3A%2F%2Fexample.com%2Frequest" // - + "&a2%3Dr%2520b%26"// - + "a3%3D2%2520q%26" + "a3%3Da%26"// - + "b5%3D%253D%25253D%26"// - + "c%2540%3D%26"// - + "c2%3D%26"// - + "oauth_consumer_key%3D9djdj82h48djs9d2%26"// - + "oauth_nonce%3DZLc92RAkooZcIO%252F0cctl0Q%253D%253D%26"// - + "oauth_signature_method%3DHMAC-SHA1%26"// - + "oauth_timestamp%3D137131201%26"// - + "oauth_token%3Dkkk9d7dh3k39sjv7%26"// - + "oauth_version%3D1.0"); - } - - @Test(groups = "fast") - public void testSignatureBaseStringWithProperlyEncodedUri() { - - Request request = new RequestBuilder("POST")// - .setUrl("http://example.com/request?b5=%3D%253D&a3=a&c%40=&a2=r%20b")// - .addFormParam("c2", "")// - .addFormParam("a3", "2 q")// - .build(); - - testSignatureBaseString(request); - testSignatureBaseStringWithEncodableOAuthToken(request); - } - - @Test(groups = "fast") - public void testSignatureBaseStringWithRawUri() { - - // note: @ is legal so don't decode it into %40 because it won't be - // encoded back - // note: we don't know how to fix a = that should have been encoded as - // %3D but who would be stupid enough to do that? - Request request = new RequestBuilder("POST")// - .setUrl("http://example.com/request?b5=%3D%253D&a3=a&c%40=&a2=r%20b")// - .addFormParam("c2", "")// - .addFormParam("a3", "2 q")// - .build(); - - testSignatureBaseString(request); - testSignatureBaseStringWithEncodableOAuthToken(request); - } - - // based on the reference test case from - // http://oauth.pbwiki.com/TestCases - @Test(groups = "fast") - public void testGetCalculateSignature() { - 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", Uri.create(url), TIMESTAMP, NONCE, null, queryParams); - - assertEquals(sig, "tR3+Ty81lMeYAr/Fid0kMTYa/WM="); - } - - @Test(groups = "fast") - public void testPostCalculateSignature() { - ConsumerKey consumer = new ConsumerKey(CONSUMER_KEY, CONSUMER_SECRET); - RequestToken user = new RequestToken(TOKEN_KEY, TOKEN_SECRET); - OAuthSignatureCalculator calc = new StaticOAuthSignatureCalculator(consumer, user, TIMESTAMP, NONCE); - - List formParams = new ArrayList(); - formParams.add(new Param("file", "vacation.jpg")); - formParams.add(new Param("size", "original")); - String url = "http://photos.example.net/photos"; - final Request req = new RequestBuilder("POST")// - .setUri(Uri.create(url))// - .setFormParams(formParams)// - .setSignatureCalculator(calc)// - .build(); - - // From the signature tester, POST should look like: - // normalized parameters: - // file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original - // signature base string: - // POST&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal - // signature: wPkvxykrw+BTdCcGqKr+3I+PsiM= - // header: OAuth - // realm="",oauth_version="1.0",oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_signature_method="HMAC-SHA1",oauth_signature="wPkvxykrw%2BBTdCcGqKr%2B3I%2BPsiM%3D" - - String authHeader = req.getHeaders().get("Authorization").get(0); - Matcher m = Pattern.compile("oauth_signature=\"(.+?)\"").matcher(authHeader); - assertEquals(m.find(), true); - String encodedSig = m.group(1); - String sig = null; - try { - sig = URLDecoder.decode(encodedSig, "UTF-8"); - } catch (UnsupportedEncodingException e) { - fail("bad encoding", e); - } - - assertEquals(sig, "wPkvxykrw+BTdCcGqKr+3I+PsiM="); - } - - @Test(groups = "fast") - public void testGetWithRequestBuilder() { - ConsumerKey consumer = new ConsumerKey(CONSUMER_KEY, CONSUMER_SECRET); - RequestToken user = new RequestToken(TOKEN_KEY, TOKEN_SECRET); - OAuthSignatureCalculator calc = new StaticOAuthSignatureCalculator(consumer, user, TIMESTAMP, NONCE); - - List queryParams = new ArrayList(); - queryParams.add(new Param("file", "vacation.jpg")); - queryParams.add(new Param("size", "original")); - String url = "http://photos.example.net/photos"; - - final Request req = new RequestBuilder("GET")// - .setUri(Uri.create(url))// - .setQueryParams(queryParams)// - .setSignatureCalculator(calc)// - .build(); - - final List params = req.getQueryParams(); - assertEquals(params.size(), 2); - - // From the signature tester, the URL should look like: - // normalized parameters: - // file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original - // signature base string: - // GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal - // signature: tR3+Ty81lMeYAr/Fid0kMTYa/WM= - // Authorization header: OAuth - // realm="",oauth_version="1.0",oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_signature_method="HMAC-SHA1",oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D" - - String authHeader = req.getHeaders().get("Authorization").get(0); - Matcher m = Pattern.compile("oauth_signature=\"(.+?)\"").matcher(authHeader); - assertEquals(m.find(), true); - String encodedSig = m.group(1); - String sig = null; - try { - sig = URLDecoder.decode(encodedSig, "UTF-8"); - } catch (UnsupportedEncodingException e) { - fail("bad encoding", e); - } - - assertEquals(sig, "tR3+Ty81lMeYAr/Fid0kMTYa/WM="); - - } - -} diff --git a/api/src/test/java/org/asynchttpclient/proxy/ProxyTest.java b/api/src/test/java/org/asynchttpclient/proxy/ProxyTest.java deleted file mode 100644 index 8519808c64..0000000000 --- a/api/src/test/java/org/asynchttpclient/proxy/ProxyTest.java +++ /dev/null @@ -1,302 +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.proxy; - -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 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; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.asynchttpclient.config.AsyncHttpClientConfigDefaults; -import org.asynchttpclient.config.AsyncHttpClientConfigHelper; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.util.ProxyUtils; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -/** - * 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 { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"), "/"); - } - } - - @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(); - try (AsyncHttpClient client = getAsyncHttpClient(cfg)) { - 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"), "/"); - } - } - - @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(); - try (AsyncHttpClient client = getAsyncHttpClient(cfg)) { - 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"), "/"); - } - } - - @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(); - try (AsyncHttpClient client = getAsyncHttpClient(cfg)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testNonProxyHostIssue202() throws IOException, ExecutionException, TimeoutException, InterruptedException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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"), "/"); - } - } - - @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 { - // 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(); - - AsyncHttpClientConfig cfg = new AsyncHttpClientConfig.Builder().setUseProxyProperties(true).build(); - - try (AsyncHttpClient client = getAsyncHttpClient(cfg)) { - 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 { - System.setProperties(originalProps); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void testIgnoreProxyPropertiesByDefault() 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 = getAsyncHttpClient(null)) { - 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 { - System.setProperties(originalProps); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - 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 = getAsyncHttpClient(null)) { - 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 { - System.setProperties(originalProps); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void testWildcardNonProxyHosts() 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, "127.*"); - AsyncHttpClientConfigHelper.reloadProperties(); - - AsyncHttpClientConfig cfg = new AsyncHttpClientConfig.Builder().setUseProxyProperties(true).build(); - try (AsyncHttpClient client = getAsyncHttpClient(cfg)) { - 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 { - System.setProperties(originalProps); - } - } - - // @Test(groups = { "standalone", "default_provider" }) - public void testUseProxySelector() throws IOException, ExecutionException, TimeoutException, InterruptedException { - ProxySelector originalProxySelector = ProxySelector.getDefault(); - 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(); - try (AsyncHttpClient client = getAsyncHttpClient(cfg)) { - 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 { - // FIXME not threadsafe - ProxySelector.setDefault(originalProxySelector); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/proxy/ProxyTunnellingTest.java b/api/src/test/java/org/asynchttpclient/proxy/ProxyTunnellingTest.java deleted file mode 100644 index 2d6b13a1b7..0000000000 --- a/api/src/test/java/org/asynchttpclient/proxy/ProxyTunnellingTest.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.proxy; - -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.newJettyHttpServer; -import static org.asynchttpclient.test.TestUtils.newJettyHttpsServer; -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncCompletionHandlerBase; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.simple.SimpleAsyncHttpClient; -import org.asynchttpclient.test.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 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(); - - try (AsyncHttpClient asyncHttpClient = getAsyncHttpClient(config)) { - 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"); - } - } - - @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(); - try (AsyncHttpClient asyncHttpClient = getAsyncHttpClient(config)) { - 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"); - } - } - - @Test(groups = { "online", "default_provider" }) - public void testSimpleAHCConfigProxy() throws IOException, InterruptedException, ExecutionException, TimeoutException { - - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder()// - .setProxyProtocol(ProxyServer.Protocol.HTTPS)// - .setProxyHost("127.0.0.1")// - .setProxyPort(port1)// - .setFollowRedirect(true)// - .setUrl(getTargetUrl2())// - .setAcceptAnyCertificate(true)// - .setHeader("Content-Type", "text/html")// - .build()) { - Response r = client.get().get(); - - assertEquals(r.getStatusCode(), 200); - assertEquals(r.getHeader("X-Connection"), "keep-alive"); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/proxy/ProxyUtilsTest.java b/api/src/test/java/org/asynchttpclient/proxy/ProxyUtilsTest.java deleted file mode 100644 index ae15c3ac64..0000000000 --- a/api/src/test/java/org/asynchttpclient/proxy/ProxyUtilsTest.java +++ /dev/null @@ -1,49 +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.proxy; - -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.util.ProxyUtils; -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/request/body/BodyChunkTest.java b/api/src/test/java/org/asynchttpclient/request/body/BodyChunkTest.java deleted file mode 100644 index 41d2729b5b..0000000000 --- a/api/src/test/java/org/asynchttpclient/request/body/BodyChunkTest.java +++ /dev/null @@ -1,57 +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.request.body; - -import static org.testng.Assert.assertEquals; - -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 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.setConnectTimeout(100); - confbuilder = confbuilder.setMaxConnections(50); - confbuilder = confbuilder.setRequestTimeout(5 * 60 * 1000); // 5 minutes - - // Create client - try (AsyncHttpClient client = getAsyncHttpClient(confbuilder.build())) { - 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); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/request/body/ChunkingTest.java b/api/src/test/java/org/asynchttpclient/request/body/ChunkingTest.java deleted file mode 100644 index 15ba7e012b..0000000000 --- a/api/src/test/java/org/asynchttpclient/request/body/ChunkingTest.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.request.body; - -import static org.asynchttpclient.test.TestUtils.LARGE_IMAGE_BYTES; -import static org.asynchttpclient.test.TestUtils.LARGE_IMAGE_FILE; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; -import static org.testng.FileAssert.fail; - -import java.io.BufferedInputStream; -import java.io.FileInputStream; -import java.io.InputStream; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.request.body.generator.InputStreamBodyGenerator; -import org.testng.annotations.Test; - -/** - * 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. - @Test() - public void testBufferLargerThanFile() throws Throwable { - doTest(new BufferedInputStream(new FileInputStream(LARGE_IMAGE_FILE), 400000)); - } - - @Test() - public void testBufferSmallThanFile() throws Throwable { - doTest(new BufferedInputStream(new FileInputStream(LARGE_IMAGE_FILE))); - } - - @Test() - public void testDirectFile() throws Throwable { - doTest(new FileInputStream(LARGE_IMAGE_FILE)); - } - - public void doTest(InputStream is) throws Throwable { - AsyncHttpClientConfig.Builder bc = new AsyncHttpClientConfig.Builder()// - .setAllowPoolingConnections(true)// - .setMaxConnectionsPerHost(1)// - .setMaxConnections(1)// - .setConnectTimeout(1000)// - .setRequestTimeout(1000) - .setFollowRedirect(true); - - try (AsyncHttpClient c = getAsyncHttpClient(bc.build())) { - - RequestBuilder builder = new RequestBuilder("POST"); - builder.setUrl(getTargetUrl()); - builder.setBody(new InputStreamBodyGenerator(is)); - - 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); - } - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/request/body/EmptyBodyTest.java b/api/src/test/java/org/asynchttpclient/request/body/EmptyBodyTest.java deleted file mode 100644 index 468060392f..0000000000 --- a/api/src/test/java/org/asynchttpclient/request/body/EmptyBodyTest.java +++ /dev/null @@ -1,137 +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.request.body; - -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.AbstractBasicTest; -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 { - try (AsyncHttpClient ahc = getAsyncHttpClient(null)) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testPutEmptyBody() throws Exception { - try (AsyncHttpClient ahc = getAsyncHttpClient(null)) { - 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); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/request/body/FastUnauthorizedUploadTest.java b/api/src/test/java/org/asynchttpclient/request/body/FastUnauthorizedUploadTest.java deleted file mode 100644 index d3771038f8..0000000000 --- a/api/src/test/java/org/asynchttpclient/request/body/FastUnauthorizedUploadTest.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.request.body; - -import static java.nio.charset.StandardCharsets.*; -import static org.asynchttpclient.test.TestUtils.createTempFile; -import static org.testng.Assert.assertEquals; - -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 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); - - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - Response response = client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", file, "application/octet-stream", UTF_8)).execute() - .get(); - assertEquals(response.getStatusCode(), 401); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/request/body/FilePartLargeFileTest.java b/api/src/test/java/org/asynchttpclient/request/body/FilePartLargeFileTest.java deleted file mode 100644 index b27164b2fb..0000000000 --- a/api/src/test/java/org/asynchttpclient/request/body/FilePartLargeFileTest.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.request.body; - -import static java.nio.charset.StandardCharsets.*; -import static org.asynchttpclient.test.TestUtils.LARGE_IMAGE_FILE; -import static org.asynchttpclient.test.TestUtils.createTempFile; -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -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 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 { - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(100 * 6000).build())) { - Response response = client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", LARGE_IMAGE_FILE, "application/octet-stream", UTF_8)).execute().get(); - assertEquals(response.getStatusCode(), 200); - } - } - - @Test(groups = { "standalone", "default_provider" }, enabled = true) - public void testPutLargeTextFile() throws Exception { - File file = createTempFile(1024 * 1024); - - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(100 * 6000).build())) { - Response response = client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", file, "application/octet-stream", UTF_8)).execute().get(); - assertEquals(response.getStatusCode(), 200); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/request/body/InputStreamTest.java b/api/src/test/java/org/asynchttpclient/request/body/InputStreamTest.java deleted file mode 100644 index 7e2c9ea153..0000000000 --- a/api/src/test/java/org/asynchttpclient/request/body/InputStreamTest.java +++ /dev/null @@ -1,106 +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.request.body; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; - -import org.asynchttpclient.AbstractBasicTest; -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 { - - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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"); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/request/body/PutLargeFileTest.java b/api/src/test/java/org/asynchttpclient/request/body/PutLargeFileTest.java deleted file mode 100644 index 3602a84817..0000000000 --- a/api/src/test/java/org/asynchttpclient/request/body/PutLargeFileTest.java +++ /dev/null @@ -1,76 +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.request.body; - -import static org.asynchttpclient.test.TestUtils.createTempFile; -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AbstractBasicTest; -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; - - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectTimeout(timeout).build())) { - Response response = client.preparePut(getTargetUrl()).setBody(file).execute().get(); - assertEquals(response.getStatusCode(), 200); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testPutSmallFile() throws Exception { - - File file = createTempFile(1024); - - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - Response response = client.preparePut(getTargetUrl()).setBody(file).execute().get(); - assertEquals(response.getStatusCode(), 200); - } - } - - @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/request/body/TransferListenerTest.java b/api/src/test/java/org/asynchttpclient/request/body/TransferListenerTest.java deleted file mode 100644 index 25074ae7e2..0000000000 --- a/api/src/test/java/org/asynchttpclient/request/body/TransferListenerTest.java +++ /dev/null @@ -1,235 +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.request.body; - -import static org.asynchttpclient.test.TestUtils.createTempFile; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -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.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 { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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); - } - }); - - 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()); - } - } - - @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); - - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectTimeout(timeout).build())) { - 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); - } - }); - - 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"); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicPutFileBodyGeneratorTest() throws Exception { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - }); - - 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"); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/request/body/ZeroCopyFileTest.java b/api/src/test/java/org/asynchttpclient/request/body/ZeroCopyFileTest.java deleted file mode 100644 index 202466f922..0000000000 --- a/api/src/test/java/org/asynchttpclient/request/body/ZeroCopyFileTest.java +++ /dev/null @@ -1,188 +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.request.body; - -import static org.asynchttpclient.test.TestUtils.SIMPLE_TEXT_FILE; -import static org.asynchttpclient.test.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.AbstractBasicTest; -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.BasicHttpsTest; -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 { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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 onHeadersWritten() { - headerSent.set(true); - return State.CONTINUE; - } - - public State onContentWritten() { - 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()); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void zeroCopyPutTest() throws IOException, ExecutionException, TimeoutException, InterruptedException, URISyntaxException { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - 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); - } - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new ZeroCopyHandler(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void zeroCopyFileTest() throws IOException, ExecutionException, TimeoutException, InterruptedException, URISyntaxException { - File tmp = new File(System.getProperty("java.io.tmpdir") + File.separator + "zeroCopy.txt"); - tmp.deleteOnExit(); - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - try (FileOutputStream stream = new FileOutputStream(tmp)) { - 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()); - } - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void zeroCopyFileWithBodyManipulationTest() throws IOException, ExecutionException, TimeoutException, InterruptedException, URISyntaxException { - File tmp = new File(System.getProperty("java.io.tmpdir") + File.separator + "zeroCopy.txt"); - tmp.deleteOnExit(); - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - try (FileOutputStream stream = new FileOutputStream(tmp)) { - 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()); - } - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/request/body/generators/ByteArrayBodyGeneratorTest.java b/api/src/test/java/org/asynchttpclient/request/body/generators/ByteArrayBodyGeneratorTest.java deleted file mode 100644 index 7f4aecb76d..0000000000 --- a/api/src/test/java/org/asynchttpclient/request/body/generators/ByteArrayBodyGeneratorTest.java +++ /dev/null @@ -1,76 +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.request.body.generators; - -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.request.body.Body; -import org.asynchttpclient.request.body.generator.ByteArrayBodyGenerator; -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/request/body/multipart/MultipartBodyTest.java b/api/src/test/java/org/asynchttpclient/request/body/multipart/MultipartBodyTest.java deleted file mode 100644 index ec28b01bdb..0000000000 --- a/api/src/test/java/org/asynchttpclient/request/body/multipart/MultipartBodyTest.java +++ /dev/null @@ -1,98 +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.request.body.multipart; - -import static java.nio.charset.StandardCharsets.*; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.request.body.Body; -import org.asynchttpclient.request.body.multipart.ByteArrayPart; -import org.asynchttpclient.request.body.multipart.FilePart; -import org.asynchttpclient.request.body.multipart.MultipartUtils; -import org.asynchttpclient.request.body.multipart.Part; -import org.asynchttpclient.request.body.multipart.StringPart; -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")); - - 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/request/body/multipart/MultipartUploadTest.java b/api/src/test/java/org/asynchttpclient/request/body/multipart/MultipartUploadTest.java deleted file mode 100644 index b57a71193e..0000000000 --- a/api/src/test/java/org/asynchttpclient/request/body/multipart/MultipartUploadTest.java +++ /dev/null @@ -1,403 +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.request.body.multipart; - -import static java.nio.charset.StandardCharsets.*; -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.getClasspathFile; -import static org.asynchttpclient.test.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.AbstractBasicTest; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.request.body.multipart.ByteArrayPart; -import org.asynchttpclient.request.body.multipart.FilePart; -import org.asynchttpclient.request.body.multipart.StringPart; -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. - * @throws IOException - */ - @Test - public void testSendingSmallFilesAndByteArray() throws IOException { - 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 = File.createTempFile("textbytearray", ".txt"); - try (FileOutputStream os = new FileOutputStream(tmpFile)) { - IOUtils.write(expectedContents.getBytes(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(); - } - - 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); - - try (AsyncHttpClient c = getAsyncHttpClient(bc.build())) { - - RequestBuilder builder = new RequestBuilder("POST"); - builder.setUrl("http://localhost/" + ":" + port1 + "/upload/bob"); - builder.addBodyPart(new FilePart("file1", testResource1File, "text/plain", UTF_8)); - builder.addBodyPart(new FilePart("file2", testResource2File, "application/x-gzip", null)); - builder.addBodyPart(new StringPart("Name", "Dominic")); - builder.addBodyPart(new FilePart("file3", testResource3File, "text/plain", UTF_8)); - - builder.addBodyPart(new StringPart("Age", "3")); - builder.addBodyPart(new StringPart("Height", "shrimplike")); - builder.addBodyPart(new StringPart("Hair", "ridiculous")); - - builder.addBodyPart(new ByteArrayPart("file4", expectedContents.getBytes(UTF_8), "text/plain", UTF_8, "bytearray.txt")); - - Request r = builder.build(); - - Response res = c.executeRequest(r).get(); - - assertEquals(res.getStatusCode(), 200); - - testSentFile(expected, testFiles, res, gzipped); - - } catch (Exception e) { - e.printStackTrace(); - fail("Download Exception"); - } finally { - 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) { - - File tmp = null; - try { - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] sourceBytes = null; - try (FileInputStream 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(); - } - - tmp = new File(responseFiles[i].trim()); - logger.debug("=============================="); - logger.debug(tmp.getAbsolutePath()); - logger.debug("=============================="); - System.out.flush(); - assertTrue(tmp.exists()); - - byte[] bytes; - try (FileInputStream 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); - } - bytes = baos2.toByteArray(); - assertEquals(bytes, sourceBytes); - } - - - if (!deflate.get(i)) { - String helloString = new String(bytes); - assertEquals(helloString, expectedContents.get(i)); - } else { - try (FileInputStream 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); - 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(); - 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().toString() + "_MockUploadServlet", ".tmp"); - tmpFile.deleteOnExit(); - try (OutputStream 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()); - } - } - } - } - } 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/api/src/test/java/org/asynchttpclient/simple/SimpleAsyncClientErrorBehaviourTest.java b/api/src/test/java/org/asynchttpclient/simple/SimpleAsyncClientErrorBehaviourTest.java deleted file mode 100644 index 05c2656a2f..0000000000 --- a/api/src/test/java/org/asynchttpclient/simple/SimpleAsyncClientErrorBehaviourTest.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.simple; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.Response; -import org.asynchttpclient.simple.SimpleAsyncHttpClient; -import org.asynchttpclient.simple.SimpleAsyncHttpClient.ErrorDocumentBehaviour; -import org.asynchttpclient.simple.consumer.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 { - - @Test(groups = { "standalone", "default_provider" }) - public void testAccumulateErrorBody() throws Exception { - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder()// - .setUrl(getTargetUrl() + "/nonexistent")// - .setErrorDocumentBehaviour(ErrorDocumentBehaviour.ACCUMULATE).build()) { - 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("")); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testOmitErrorBody() throws Exception { - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder()// - .setUrl(getTargetUrl() + "/nonexistent")// - .setErrorDocumentBehaviour(ErrorDocumentBehaviour.OMIT).build()) { - 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(), ""); - } - } - - @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/simple/SimpleAsyncHttpClientTest.java b/api/src/test/java/org/asynchttpclient/simple/SimpleAsyncHttpClientTest.java deleted file mode 100644 index 8ca355259b..0000000000 --- a/api/src/test/java/org/asynchttpclient/simple/SimpleAsyncHttpClientTest.java +++ /dev/null @@ -1,345 +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; - -import static java.nio.charset.StandardCharsets.UTF_8; -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 java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.asynchttpclient.request.body.generator.FileBodyGenerator; -import org.asynchttpclient.request.body.generator.InputStreamBodyGenerator; -import org.asynchttpclient.request.body.multipart.ByteArrayPart; -import org.asynchttpclient.simple.HeaderMap; -import org.asynchttpclient.simple.SimpleAHCTransferListener; -import org.asynchttpclient.simple.SimpleAsyncHttpClient; -import org.asynchttpclient.simple.consumer.AppendableBodyConsumer; -import org.asynchttpclient.simple.consumer.OutputStreamBodyConsumer; -import org.asynchttpclient.uri.Uri; -import org.testng.annotations.Test; - -public abstract class SimpleAsyncHttpClientTest extends AbstractBasicTest { - - private final static String MY_MESSAGE = "my message"; - - /** - * Not Used - * - * @param config - * @return - */ - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - throw new UnsupportedOperationException(); - } - - @Test(groups = { "standalone", "default_provider" }) - public void inputStreamBodyConsumerTest() throws Exception { - - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder()// - .setPooledConnectionIdleTimeout(100)// - .setMaxConnections(50)// - .setRequestTimeout(5 * 60 * 1000)// - .setUrl(getTargetUrl())// - .setHeader("Content-Type", "text/html").build()) { - Future future = client.post(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes()))); - - Response response = future.get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getResponseBody(), MY_MESSAGE); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void stringBuilderBodyConsumerTest() throws Exception { - - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder()// - .setPooledConnectionIdleTimeout(100)// - .setMaxConnections(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)); - - Response response = future.get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(s.toString(), MY_MESSAGE); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void byteArrayOutputStreamBodyConsumerTest() throws Exception { - - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder()// - .setPooledConnectionIdleTimeout(100).setMaxConnections(50)// - .setRequestTimeout(5 * 60 * 1000)// - .setUrl(getTargetUrl())// - .setHeader("Content-Type", "text/html").build()) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void requestByteArrayOutputStreamBodyConsumerTest() throws Exception { - - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setUrl(getTargetUrl()).build()) { - 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); - } - } - - /** - * See https://issues.sonatype.org/browse/AHC-5 - */ - @Test(groups = { "standalone", "default_provider" }, enabled = true) - public void testPutZeroBytesFileTest() throws Exception { - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder()// - .setPooledConnectionIdleTimeout(100)// - .setMaxConnections(50)// - .setRequestTimeout(5 * 1000)// - .setUrl(getTargetUrl() + "/testPutZeroBytesFileTest.txt")// - .setHeader("Content-Type", "text/plain").build()) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testDerive() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().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 { - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setUrl("http://invalid.url/").build()) { - ByteArrayOutputStream o = new ByteArrayOutputStream(10); - - InputStreamBodyGenerator generator = new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes())); - OutputStreamBodyConsumer consumer = new OutputStreamBodyConsumer(o); - - try (SimpleAsyncHttpClient derived = client.derive().setUrl(getTargetUrl()).build()) { - Future future = derived.post(generator, consumer); - - Response response = future.get(); - assertEquals(response.getStatusCode(), 200); - assertEquals(o.toString(), MY_MESSAGE); - } - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testSimpleTransferListener() throws Exception { - - final List errors = Collections.synchronizedList(new ArrayList()); - - SimpleAHCTransferListener listener = new SimpleAHCTransferListener() { - - public void onStatus(Uri uri, int statusCode, String statusText) { - try { - assertEquals(statusCode, 200); - assertEquals(uri.toUrl(), getTargetUrl()); - } catch (Error e) { - errors.add(e); - throw e; - } - } - - public void onHeaders(Uri 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(Uri uri, int statusCode, String statusText) { - try { - assertEquals(statusCode, 200); - assertEquals(uri.toUrl(), getTargetUrl()); - } catch (Error e) { - errors.add(e); - throw e; - } - } - - public void onBytesSent(Uri 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(Uri uri, long amount, long current, long total) { - try { - assertEquals(uri.toUrl(), getTargetUrl()); - assertEquals(total, -1); - } catch (Error e) { - errors.add(e); - throw e; - } - } - }; - - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder()// - // - .setUrl(getTargetUrl())// - .setHeader("Custom", "custom")// - .setListener(listener).build()) { - 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); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testNullUrl() throws Exception { - - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().build()) { - assertTrue(true); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testCloseDerivedValidMaster() throws Exception { - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setUrl(getTargetUrl()).build()) { - try (SimpleAsyncHttpClient derived = client.derive().build()) { - derived.get().get(); - } - - Response response = client.get().get(); - assertEquals(response.getStatusCode(), 200); - } - } - - @Test(groups = { "standalone", "default_provider" }, expectedExceptions = { IllegalStateException.class }) - public void testCloseMasterInvalidDerived() throws Throwable { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setUrl(getTargetUrl()).build(); - SimpleAsyncHttpClient derived = client.derive().build(); - - client.close(); - - try { - derived.get().get(); - fail("Expected closed AHC"); - } catch (ExecutionException e) { - throw e.getCause(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testMultiPartPut() throws Exception { - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setUrl(getTargetUrl() + "/multipart").build()) { - Response response = client.put(new ByteArrayPart("baPart", "testMultiPart".getBytes(UTF_8), "application/test", 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")); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testMultiPartPost() throws Exception { - try (SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setUrl(getTargetUrl() + "/multipart").build()) { - Response response = client.post(new ByteArrayPart("baPart", "testMultiPart".getBytes(UTF_8), "application/test", 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")); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/test/EchoHandler.java b/api/src/test/java/org/asynchttpclient/test/EchoHandler.java deleted file mode 100644 index bbb97b3427..0000000000 --- a/api/src/test/java/org/asynchttpclient/test/EchoHandler.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.asynchttpclient.test; - -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/test/EventCollectingHandler.java b/api/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java deleted file mode 100644 index 325add4f30..0000000000 --- a/api/src/test/java/org/asynchttpclient/test/EventCollectingHandler.java +++ /dev/null @@ -1,116 +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.test; - -import java.net.InetAddress; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.asynchttpclient.AsyncCompletionHandlerBase; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; -import org.asynchttpclient.handler.AsyncHandlerExtensions; -import org.testng.Assert; - -public class EventCollectingHandler extends AsyncCompletionHandlerBase implements AsyncHandlerExtensions { - public Queue firedEvents = new ConcurrentLinkedQueue<>(); - private CountDownLatch completionLatch = new CountDownLatch(1); - - public void waitForCompletion(int timeout, TimeUnit unit) throws InterruptedException { - if (!completionLatch.await(timeout, unit)) { - Assert.fail("Timeout out"); - } - } - - @Override - public Response onCompleted(Response response) throws Exception { - firedEvents.add("Completed"); - try { - return super.onCompleted(response); - } finally { - completionLatch.countDown(); - } - } - - @Override - public State onStatusReceived(HttpResponseStatus status) throws Exception { - firedEvents.add("StatusReceived"); - return super.onStatusReceived(status); - } - - @Override - public State onHeadersReceived(HttpResponseHeaders headers) throws Exception { - firedEvents.add("HeadersReceived"); - return super.onHeadersReceived(headers); - } - - @Override - public State onHeadersWritten() { - firedEvents.add("HeadersWritten"); - return super.onHeadersWritten(); - } - - @Override - public State onContentWritten() { - firedEvents.add("ContentWritten"); - return super.onContentWritten(); - } - - @Override - public void onConnectionOpen() { - firedEvents.add("ConnectionOpen"); - } - - @Override - public void onConnectionOpened(Object connection) { - firedEvents.add("ConnectionOpened"); - } - - @Override - public void onConnectionPool() { - firedEvents.add("ConnectionPool"); - } - - @Override - public void onConnectionPooled(Object connection) { - firedEvents.add("ConnectionPooled"); - } - - @Override - public void onConnectionOffer(Object connection) { - firedEvents.add("ConnectionOffer"); - } - - @Override - public void onRequestSend(Object request) { - firedEvents.add("RequestSend"); - } - - @Override - public void onRetry() { - firedEvents.add("Retry"); - } - - @Override - public void onDnsResolved(InetAddress remoteAddress) { - firedEvents.add("DnsResolved"); - } - - @Override - public void onSslHandshakeCompleted() { - firedEvents.add("SslHandshakeCompleted"); - } -} diff --git a/api/src/test/java/org/asynchttpclient/test/TestUtils.java b/api/src/test/java/org/asynchttpclient/test/TestUtils.java deleted file mode 100644 index 4a3588b1d7..0000000000 --- a/api/src/test/java/org/asynchttpclient/test/TestUtils.java +++ /dev/null @@ -1,269 +0,0 @@ -package org.asynchttpclient.test; - -import static java.nio.charset.StandardCharsets.*; -import static org.testng.Assert.assertEquals; - -import org.apache.commons.io.FileUtils; -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, UTF_8); - } catch (Exception e) { - throw new ExceptionInInitializerError(e); - } - } - - public static synchronized int findFreePort() throws IOException { - try (ServerSocket socket = new ServerSocket(0)) { - return socket.getLocalPort(); - } - } - - 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(); - try (FileOutputStream 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; - } - } - - 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, 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 { - 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/uri/UriTest.java b/api/src/test/java/org/asynchttpclient/uri/UriTest.java deleted file mode 100644 index 3f798ed7f0..0000000000 --- a/api/src/test/java/org/asynchttpclient/uri/UriTest.java +++ /dev/null @@ -1,217 +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 org.testng.annotations.Test; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; - -public class UriTest { - - @Test - public void testSimpleParsing() { - Uri url = Uri.create("https://graph.facebook.com/750198471659552/accounts/test-users?method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "graph.facebook.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/750198471659552/accounts/test-users"); - assertEquals(url.getQuery(), "method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); - } - - @Test - public void testRootRelativeURIWithRootContext() { - - Uri context = Uri.create("https://graph.facebook.com/"); - - Uri url = Uri.create(context, "/750198471659552/accounts/test-users?method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "graph.facebook.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/750198471659552/accounts/test-users"); - assertEquals(url.getQuery(), "method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); - } - - @Test - public void testRootRelativeURIWithNonRootContext() { - - Uri context = Uri.create("https://graph.facebook.com/foo/bar"); - - Uri url = Uri.create(context, "/750198471659552/accounts/test-users?method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "graph.facebook.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/750198471659552/accounts/test-users"); - assertEquals(url.getQuery(), "method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); - } - - @Test - public void testNonRootRelativeURIWithNonRootContext() { - - Uri context = Uri.create("https://graph.facebook.com/foo/bar"); - - Uri url = Uri.create(context, "750198471659552/accounts/test-users?method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "graph.facebook.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/foo/750198471659552/accounts/test-users"); - assertEquals(url.getQuery(), "method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); - } - - @Test - public void testAbsoluteURIWithContext() { - - Uri context = Uri.create("https://hello.com/foo/bar"); - - Uri url = Uri.create(context, "https://graph.facebook.com/750198471659552/accounts/test-users?method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "graph.facebook.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/750198471659552/accounts/test-users"); - assertEquals(url.getQuery(), "method=get&access_token=750198471659552lleveCvbUu_zqBa9tkT3tcgaPh4"); - } - - @Test - public void testRelativeUriWithDots() { - Uri context = Uri.create("https://hello.com/level1/level2/"); - - Uri url = Uri.create(context, "../other/content/img.png"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "hello.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/level1/other/content/img.png"); - assertNull(url.getQuery()); - } - - @Test - public void testRelativeUriWithDotsAboveRoot() { - Uri context = Uri.create("https://hello.com/level1"); - - Uri url = Uri.create(context, "../other/content/img.png"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "hello.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/../other/content/img.png"); - assertNull(url.getQuery()); - } - - @Test - public void testRelativeUriWithAbsoluteDots() { - Uri context = Uri.create("https://hello.com/level1/"); - - Uri url = Uri.create(context, "/../other/content/img.png"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "hello.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/../other/content/img.png"); - assertNull(url.getQuery()); - } - - @Test - public void testRelativeUriWithConsecutiveDots() { - Uri context = Uri.create("https://hello.com/level1/level2/"); - - Uri url = Uri.create(context, "../../other/content/img.png"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "hello.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/other/content/img.png"); - assertNull(url.getQuery()); - } - - @Test - public void testRelativeUriWithConsecutiveDotsAboveRoot() { - Uri context = Uri.create("https://hello.com/level1/level2"); - - Uri url = Uri.create(context, "../../other/content/img.png"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "hello.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/../other/content/img.png"); - assertNull(url.getQuery()); - } - - @Test - public void testRelativeUriWithAbsoluteConsecutiveDots() { - Uri context = Uri.create("https://hello.com/level1/level2/"); - - Uri url = Uri.create(context, "/../../other/content/img.png"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "hello.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/../../other/content/img.png"); - assertNull(url.getQuery()); - } - - @Test - public void testRelativeUriWithConsecutiveDotsFromRoot() { - Uri context = Uri.create("https://hello.com/"); - - Uri url = Uri.create(context, "../../../other/content/img.png"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "hello.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/../../../other/content/img.png"); - assertNull(url.getQuery()); - } - - @Test - public void testRelativeUriWithConsecutiveDotsFromRootResource() { - Uri context = Uri.create("https://hello.com/level1"); - - Uri url = Uri.create(context, "../../../other/content/img.png"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "hello.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/../../../other/content/img.png"); - assertNull(url.getQuery()); - } - - @Test - public void testRelativeUriWithConsecutiveDotsFromSubrootResource() { - Uri context = Uri.create("https://hello.com/level1/level2"); - - Uri url = Uri.create(context, "../../../other/content/img.png"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "hello.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/../../other/content/img.png"); - assertNull(url.getQuery()); - } - - @Test - public void testRelativeUriWithConsecutiveDotsFromLevel3Resource() { - Uri context = Uri.create("https://hello.com/level1/level2/level3"); - - Uri url = Uri.create(context, "../../../other/content/img.png"); - - assertEquals(url.getScheme(), "https"); - assertEquals(url.getHost(), "hello.com"); - assertEquals(url.getPort(), -1); - assertEquals(url.getPath(), "/../other/content/img.png"); - assertNull(url.getQuery()); - } -} 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 7c74caa9cc..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.encodeQueryElement("foobar"), "foobar"); - assertEquals(Utf8UrlEncoder.encodeQueryElement("a&b"), "a%26b"); - assertEquals(Utf8UrlEncoder.encodeQueryElement("a+b"), "a%2Bb"); - } -} diff --git a/api/src/test/java/org/asynchttpclient/webdav/WebDavBasicTest.java b/api/src/test/java/org/asynchttpclient/webdav/WebDavBasicTest.java deleted file mode 100644 index e8aaa5215b..0000000000 --- a/api/src/test/java/org/asynchttpclient/webdav/WebDavBasicTest.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. - */ -package org.asynchttpclient.webdav; - -import static org.asynchttpclient.test.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.AbstractBasicTest; -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 { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - Request deleteRequest = new RequestBuilder("DELETE").setUrl(getTargetUrl()).build(); - c.executeRequest(deleteRequest).get(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void mkcolWebDavTest1() throws InterruptedException, IOException, ExecutionException { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - Request mkcolRequest = new RequestBuilder("MKCOL").setUrl(getTargetUrl()).build(); - Response response = c.executeRequest(mkcolRequest).get(); - assertEquals(response.getStatusCode(), 201); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void mkcolWebDavTest2() throws InterruptedException, IOException, ExecutionException { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - Request mkcolRequest = new RequestBuilder("MKCOL").setUrl(getTargetUrl() + "/folder2").build(); - Response response = c.executeRequest(mkcolRequest).get(); - assertEquals(response.getStatusCode(), 409); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void basicPropFindWebDavTest() throws InterruptedException, IOException, ExecutionException { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - Request propFindRequest = new RequestBuilder("PROPFIND").setUrl(getTargetUrl()).build(); - Response response = c.executeRequest(propFindRequest).get(); - - assertEquals(response.getStatusCode(), 404); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void propFindWebDavTest() throws InterruptedException, IOException, ExecutionException { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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")); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void propFindCompletionHandlerWebDavTest() throws InterruptedException, IOException, ExecutionException { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/ws/AbstractBasicTest.java b/api/src/test/java/org/asynchttpclient/ws/AbstractBasicTest.java deleted file mode 100644 index 2c74a0dfa3..0000000000 --- a/api/src/test/java/org/asynchttpclient/ws/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.ws; - -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.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.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/ws/ByteMessageTest.java b/api/src/test/java/org/asynchttpclient/ws/ByteMessageTest.java deleted file mode 100644 index b1371a70b9..0000000000 --- a/api/src/test/java/org/asynchttpclient/ws/ByteMessageTest.java +++ /dev/null @@ -1,209 +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.ws; - -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.ws.WebSocket; -import org.asynchttpclient.ws.WebSocketByteListener; -import org.asynchttpclient.ws.WebSocketUpgradeHandler; -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 { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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()); - } - } - - @Test - public void echoTwoMessagesTest() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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()); - } - } - - @Test - public void echoOnOpenMessagesTest() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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()); - } - } - - public void echoFragments() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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()); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/ws/CloseCodeReasonMessageTest.java b/api/src/test/java/org/asynchttpclient/ws/CloseCodeReasonMessageTest.java deleted file mode 100644 index 182c778457..0000000000 --- a/api/src/test/java/org/asynchttpclient/ws/CloseCodeReasonMessageTest.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.ws; - -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.ws.WebSocket; -import org.asynchttpclient.ws.WebSocketCloseCodeReasonListener; -import org.asynchttpclient.ws.WebSocketListener; -import org.asynchttpclient.ws.WebSocketTextListener; -import org.asynchttpclient.ws.WebSocketUpgradeHandler; -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.ExecutionException; -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 { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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")); - } - } - - @Test(timeOut = 60000) - public void onCloseWithCodeServerClose() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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"); - } - } - - 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, expectedExceptions = { ExecutionException.class }) - public void getWebSocketThrowsException() throws Throwable { - final CountDownLatch latch = new CountDownLatch(1); - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - client.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) { - latch.countDown(); - } - }).build()).get(); - } - - latch.await(); - } - - @Test(timeOut = 60000, expectedExceptions = { IllegalArgumentException.class } ) - public void wrongStatusCode() throws Throwable { - try (AsyncHttpClient client = getAsyncHttpClient(null)) { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference throwable = new AtomicReference<>(); - - client.prepareGet("http://apache.org/").execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { - - @Override - public void onMessage(String message) { - } - - @Override - public void onOpen(org.asynchttpclient.ws.WebSocket websocket) { - } - - @Override - public void onClose(org.asynchttpclient.ws.WebSocket websocket) { - } - - @Override - public void onError(Throwable t) { - throwable.set(t); - latch.countDown(); - } - }).build()); - - latch.await(); - assertNotNull(throwable.get()); - throw throwable.get(); - } - } - - @Test(timeOut = 60000, expectedExceptions = { IllegalStateException.class } ) - public void wrongProtocolCode() throws Throwable { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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()); - - latch.await(); - assertNotNull(throwable.get()); - throw throwable.get(); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/ws/EchoSocket.java b/api/src/test/java/org/asynchttpclient/ws/EchoSocket.java deleted file mode 100644 index dcb7d75ea6..0000000000 --- a/api/src/test/java/org/asynchttpclient/ws/EchoSocket.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.asynchttpclient.ws; - -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); - } - - @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 { - 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/ws/ProxyTunnellingTest.java b/api/src/test/java/org/asynchttpclient/ws/ProxyTunnellingTest.java deleted file mode 100644 index 04eb41170a..0000000000 --- a/api/src/test/java/org/asynchttpclient/ws/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.ws; - -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.newJettyHttpServer; -import static org.asynchttpclient.test.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.proxy.ProxyServer; -import org.asynchttpclient.ws.WebSocket; -import org.asynchttpclient.ws.WebSocketTextListener; -import org.asynchttpclient.ws.WebSocketUpgradeHandler; -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(); - try (AsyncHttpClient asyncHttpClient = getAsyncHttpClient(config)) { - 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.sendMessage("ECHO"); - - latch.await(); - assertEquals(text.get(), "ECHO"); - } - } -} diff --git a/api/src/test/java/org/asynchttpclient/ws/RedirectTest.java b/api/src/test/java/org/asynchttpclient/ws/RedirectTest.java deleted file mode 100644 index c082ea78b8..0000000000 --- a/api/src/test/java/org/asynchttpclient/ws/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.ws; - -import static org.asynchttpclient.test.TestUtils.addHttpConnector; -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ws.WebSocket; -import org.asynchttpclient.ws.WebSocketListener; -import org.asynchttpclient.ws.WebSocketUpgradeHandler; -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 { - try (AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build())) { - 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(); - } - } - - private String getRedirectURL() { - return String.format("ws://127.0.0.1:%d/", port2); - } -} diff --git a/api/src/test/java/org/asynchttpclient/ws/TextMessageTest.java b/api/src/test/java/org/asynchttpclient/ws/TextMessageTest.java deleted file mode 100644 index 0a1b307b04..0000000000 --- a/api/src/test/java/org/asynchttpclient/ws/TextMessageTest.java +++ /dev/null @@ -1,369 +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.ws; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import java.net.ConnectException; -import java.net.UnknownHostException; -import java.nio.channels.UnresolvedAddressException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicReference; - -import org.asynchttpclient.AsyncHttpClient; -import org.eclipse.jetty.websocket.server.WebSocketHandler; -import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.testng.annotations.Test; - -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 { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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"); - } - } - - @Test(timeOut = 60000) - public void onEmptyListenerTest() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - WebSocket websocket = null; - try { - websocket = c.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().build()).get(); - } catch (Throwable t) { - fail(); - } - assertTrue(websocket != null); - } - } - - @Test(timeOut = 60000, expectedExceptions = { ConnectException.class, UnresolvedAddressException.class, UnknownHostException.class }) - public void onFailureTest() throws Throwable { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - c.prepareGet("ws://abcdefg").execute(new WebSocketUpgradeHandler.Builder().build()).get(); - } catch (ExecutionException e) { - if (e.getCause() != null) - throw e.getCause(); - else - throw e; - } - } - - @Test(timeOut = 60000) - public void onTimeoutCloseTest() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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"); - } - } - - @Test(timeOut = 60000) - public void onClose() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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"); - } - } - - @Test(timeOut = 60000) - public void echoText() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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.sendMessage("ECHO"); - - latch.await(); - assertEquals(text.get(), "ECHO"); - } - } - - @Test(timeOut = 60000) - public void echoDoubleListenerText() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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.sendMessage("ECHO"); - - latch.await(); - assertEquals(text.get(), "ECHOECHO"); - } - } - - @Test - public void echoTwoMessagesTest() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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.sendMessage("ECHO").sendMessage("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"); - } - } - - public void echoFragments() throws Exception { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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.stream("ECHO", false); - websocket.stream("ECHO", true); - - latch.await(); - assertEquals(text.get(), "ECHOECHO"); - } - } - - @Test(timeOut = 60000) - public void echoTextAndThenClose() throws Throwable { - try (AsyncHttpClient c = getAsyncHttpClient(null)) { - 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.sendMessage("ECHO"); - textLatch.await(); - - websocket.sendMessage("CLOSE"); - closeLatch.await(); - - assertEquals(text.get(), "ECHO"); - } - } -} 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 d6e94a7079..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: * @@ -16,7 +16,6 @@ package org.asynchttpclient; - /** * Interface that allows injecting signature calculator into * {@link RequestBuilder} so that signature calculation and inclusion can @@ -24,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 @@ -37,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/api/src/main/java/org/asynchttpclient/handler/ProgressAsyncHandler.java b/client/src/main/java/org/asynchttpclient/handler/ProgressAsyncHandler.java similarity index 88% rename from api/src/main/java/org/asynchttpclient/handler/ProgressAsyncHandler.java rename to client/src/main/java/org/asynchttpclient/handler/ProgressAsyncHandler.java index e46fcea106..04839e2760 100644 --- a/api/src/main/java/org/asynchttpclient/handler/ProgressAsyncHandler.java +++ b/client/src/main/java/org/asynchttpclient/handler/ProgressAsyncHandler.java @@ -15,6 +15,9 @@ 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. @@ -22,7 +25,7 @@ public interface ProgressAsyncHandler extends AsyncHandler { /** - * Invoked when the content (a {@link java.io.File}, {@link String} or {@link java.io.FileInputStream} has been fully + * Invoked when the content (a {@link File}, {@link String} or {@link FileInputStream}) has been fully * written on the I/O socket. * * @return a {@link AsyncHandler.State} telling to CONTINUE or ABORT the current processing. @@ -30,7 +33,7 @@ public interface ProgressAsyncHandler extends AsyncHandler { State onHeadersWritten(); /** - * Invoked when the content (a {@link java.io.File}, {@link String} or {@link java.io.FileInputStream} has been fully + * Invoked when the content (a {@link File}, {@link String} or {@link FileInputStream}) has been fully * written on the I/O socket. * * @return a {@link AsyncHandler.State} telling to CONTINUE or ABORT the current processing. diff --git a/client/src/main/java/org/asynchttpclient/handler/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/handler/TransferListener.java b/client/src/main/java/org/asynchttpclient/handler/TransferListener.java similarity index 78% rename from api/src/main/java/org/asynchttpclient/handler/TransferListener.java rename to client/src/main/java/org/asynchttpclient/handler/TransferListener.java index c544c4bbf8..fd50dcf7e6 100644 --- a/api/src/main/java/org/asynchttpclient/handler/TransferListener.java +++ b/client/src/main/java/org/asynchttpclient/handler/TransferListener.java @@ -12,31 +12,33 @@ */ 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/api/src/main/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcessor.java b/client/src/main/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcessor.java similarity index 77% rename from api/src/main/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcessor.java rename to client/src/main/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcessor.java index d02c445774..3a65f019e7 100644 --- a/api/src/main/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcessor.java +++ b/client/src/main/java/org/asynchttpclient/handler/resumable/PropertiesBasedResumableProcessor.java @@ -12,40 +12,41 @@ */ package org.asynchttpclient.handler.resumable; -import static java.nio.charset.StandardCharsets.*; -import static org.asynchttpclient.util.MiscUtils.closeSilently; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; +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 org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.asynchttpclient.util.MiscUtils.closeSilently; /** - * A {@link org.asynchttpclient.handler.resumable.ResumableAsyncHandler.ResumableProcessor} which use a properties file + * A {@link 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 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<>(); - /** - * {@inheritDoc} - */ + 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); } - /** - * {@inheritDoc} - */ @Override public void remove(String uri) { if (uri != null) { @@ -53,13 +54,10 @@ public void remove(String uri) { } } - /** - * {@inheritDoc} - */ @Override public void save(Map map) { - log.debug("Saving current download state {}", properties.toString()); - FileOutputStream os = null; + log.debug("Saving current download state {}", properties); + OutputStream os = null; try { if (!TMP.exists() && !TMP.mkdirs()) { @@ -73,8 +71,7 @@ public void save(Map map) { throw new IllegalStateException(); } - os = new FileOutputStream(f); - + os = Files.newOutputStream(f.toPath()); for (Map.Entry e : properties.entrySet()) { os.write(append(e).getBytes(UTF_8)); } @@ -82,23 +79,15 @@ public void save(Map map) { } catch (Throwable e) { log.warn(e.getMessage(), e); } finally { - if (os != null) - closeSilently(os); + 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), UTF_8.name()); + scan = new Scanner(new File(TMP, storeName), UTF_8); scan.useDelimiter("[=\n]"); String key; @@ -108,16 +97,17 @@ public Map load() { value = scan.next().trim(); properties.put(key, Long.valueOf(value)); } - log.debug("Loading previous download state {}", properties.toString()); + 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) + if (scan != null) { scan.close(); + } } - return properties; + return Collections.unmodifiableMap(properties); } } diff --git a/api/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java similarity index 77% rename from api/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java rename to client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java index 38d101ad3f..6b8794547a 100644 --- a/api/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java +++ b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableAsyncHandler.java @@ -12,47 +12,55 @@ */ package org.asynchttpclient.handler.resumable; +import io.netty.handler.codec.http.HttpHeaders; 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.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 prevent having to - * download the entire file again. It's the responsibility of the {@link org.asynchttpclient.handler.resumable.ResumableAsyncHandler} + * 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 final static Logger logger = LoggerFactory.getLogger(TransferCompletionHandler.class); + 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 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 @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, ResumableProcessor resumableProcessor, - AsyncHandler decoratedAsyncHandler, boolean accumulateBody) { + private ResumableAsyncHandler(long byteTransferred, @Nullable ResumableProcessor resumableProcessor, + @Nullable AsyncHandler decoratedAsyncHandler, boolean accumulateBody) { this.byteTransferred = new AtomicLong(byteTransferred); @@ -96,11 +104,8 @@ public ResumableAsyncHandler(ResumableProcessor resumableProcessor, boolean accu this(0, resumableProcessor, null, accumulateBody); } - /** - * {@inheritDoc} - */ @Override - public AsyncHandler.State onStatusReceived(final HttpResponseStatus status) throws Exception { + public State onStatusReceived(final HttpResponseStatus status) throws Exception { responseBuilder.accumulate(status); if (status.getStatusCode() == 200 || status.getStatusCode() == 206) { url = status.getUri().toUrl(); @@ -115,9 +120,6 @@ public AsyncHandler.State onStatusReceived(final HttpResponseStatus status) thro return AsyncHandler.State.CONTINUE; } - /** - * {@inheritDoc} - */ @Override public void onThrowable(Throwable t) { if (decoratedAsyncHandler != null) { @@ -127,11 +129,8 @@ public void onThrowable(Throwable t) { } } - /** - * {@inheritDoc} - */ @Override - public AsyncHandler.State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { + public State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { if (accumulateBody) { responseBuilder.accumulate(bodyPart); @@ -154,11 +153,8 @@ public AsyncHandler.State onBodyPartReceived(HttpResponseBodyPart bodyPart) thro return state; } - /** - * {@inheritDoc} - */ @Override - public Response onCompleted() throws Exception { + public @Nullable Response onCompleted() throws Exception { resumableProcessor.remove(url); resumableListener.onAllBytesReceived(); @@ -169,13 +165,10 @@ public Response onCompleted() throws Exception { return responseBuilder.build(); } - /** - * {@inheritDoc} - */ @Override - public AsyncHandler.State onHeadersReceived(HttpResponseHeaders headers) throws Exception { + public State onHeadersReceived(HttpHeaders headers) throws Exception { responseBuilder.accumulate(headers); - String contentLengthHeader = headers.getHeaders().getFirstValue("Content-Length"); + String contentLengthHeader = headers.get(CONTENT_LENGTH); if (contentLengthHeader != null) { if (Long.parseLong(contentLengthHeader) == -1L) { return AsyncHandler.State.ABORT; @@ -185,7 +178,13 @@ public AsyncHandler.State onHeadersReceived(HttpResponseHeaders headers) throws if (decoratedAsyncHandler != null) { return decoratedAsyncHandler.onHeadersReceived(headers); } - return AsyncHandler.State.CONTINUE; + return State.CONTINUE; + } + + @Override + public State onTrailingHeadersReceived(HttpHeaders headers) { + responseBuilder.accumulate(headers); + return State.CONTINUE; } /** @@ -196,20 +195,19 @@ public AsyncHandler.State onHeadersReceived(HttpResponseHeaders headers) throws * @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 Resumbale + // The Resumable if (resumableListener != null && resumableListener.length() > 0 && byteTransferred.get() != resumableListener.length()) { byteTransferred.set(resumableListener.length()); } - RequestBuilder builder = new RequestBuilder(request); - if (request.getHeaders().get("Range").isEmpty() && byteTransferred.get() != 0) { - builder.setHeader("Range", "bytes=" + byteTransferred.get() + "-"); + RequestBuilder builder = request.toBuilder(); + if (request.getHeaders().get(RANGE) == null && byteTransferred.get() != 0) { + builder.setHeader(RANGE, "bytes=" + byteTransferred.get() + '-'); } return builder.build(); } @@ -225,29 +223,10 @@ public ResumableAsyncHandler setResumableListener(ResumableListener resumableLis 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 { + public interface ResumableProcessor { /** * Associate a key with the number of bytes successfully transferred. @@ -268,30 +247,53 @@ public static interface ResumableProcessor { * 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 + * @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} + * @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<>(); } @@ -299,18 +301,24 @@ public Map load() { private static class NULLResumableListener implements ResumableListener { - private long length = 0L; + private long length; - public void onBytesReceived(ByteBuffer byteBuffer) throws IOException { + 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/handler/resumable/ResumableListener.java b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableListener.java similarity index 90% rename from api/src/main/java/org/asynchttpclient/handler/resumable/ResumableListener.java rename to client/src/main/java/org/asynchttpclient/handler/resumable/ResumableListener.java index 9570c35b0f..4c4c6cbffd 100644 --- a/api/src/main/java/org/asynchttpclient/handler/resumable/ResumableListener.java +++ b/client/src/main/java/org/asynchttpclient/handler/resumable/ResumableListener.java @@ -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 84% rename from api/src/main/java/org/asynchttpclient/extra/ResumableRandomAccessFileListener.java rename to client/src/main/java/org/asynchttpclient/handler/resumable/ResumableRandomAccessFileListener.java index 0faf103483..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.handler.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.handler.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/api/src/main/java/org/asynchttpclient/ntlm/NtlmEngineException.java b/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngineException.java similarity index 90% rename from api/src/main/java/org/asynchttpclient/ntlm/NtlmEngineException.java rename to client/src/main/java/org/asynchttpclient/ntlm/NtlmEngineException.java index 23f005ebf9..dd7827cbf7 100644 --- a/api/src/main/java/org/asynchttpclient/ntlm/NtlmEngineException.java +++ b/client/src/main/java/org/asynchttpclient/ntlm/NtlmEngineException.java @@ -23,14 +23,15 @@ * . * */ - package org.asynchttpclient.ntlm; +import org.jetbrains.annotations.Nullable; + /** * Signals NTLM protocol failure. */ -public class NtlmEngineException extends RuntimeException { +class NtlmEngineException extends RuntimeException { private static final long serialVersionUID = 6027981323731768824L; @@ -39,7 +40,7 @@ public class NtlmEngineException extends RuntimeException { * * @param message the exception detail message */ - public NtlmEngineException(String message) { + NtlmEngineException(String message) { super(message); } @@ -50,8 +51,7 @@ public NtlmEngineException(String 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) { + 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/request/body/generator/BodyGenerator.java b/client/src/main/java/org/asynchttpclient/request/body/generator/BodyGenerator.java similarity index 93% rename from api/src/main/java/org/asynchttpclient/request/body/generator/BodyGenerator.java rename to client/src/main/java/org/asynchttpclient/request/body/generator/BodyGenerator.java index 4b20ee978f..835ef7bd60 100644 --- a/api/src/main/java/org/asynchttpclient/request/body/generator/BodyGenerator.java +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/BodyGenerator.java @@ -10,7 +10,6 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.Body; @@ -18,12 +17,13 @@ /** * 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}. */ 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/api/src/main/java/org/asynchttpclient/request/body/generator/FileBodyGenerator.java b/client/src/main/java/org/asynchttpclient/request/body/generator/FileBodyGenerator.java similarity index 91% rename from api/src/main/java/org/asynchttpclient/request/body/generator/FileBodyGenerator.java rename to client/src/main/java/org/asynchttpclient/request/body/generator/FileBodyGenerator.java index acbe389812..82bc02111c 100644 --- a/api/src/main/java/org/asynchttpclient/request/body/generator/FileBodyGenerator.java +++ b/client/src/main/java/org/asynchttpclient/request/body/generator/FileBodyGenerator.java @@ -12,9 +12,11 @@ */ package org.asynchttpclient.request.body.generator; +import org.asynchttpclient.request.body.RandomAccessBody; + import java.io.File; -import org.asynchttpclient.request.body.RandomAccessBody; +import static java.util.Objects.requireNonNull; /** * Creates a request body from the contents of a file. @@ -30,10 +32,7 @@ public FileBodyGenerator(File file) { } public FileBodyGenerator(File file, long regionSeek, long regionLength) { - if (file == null) { - throw new NullPointerException("file"); - } - this.file = file; + this.file = requireNonNull(file, "file"); this.regionLength = regionLength; this.regionSeek = regionSeek; } @@ -50,9 +49,6 @@ public long getRegionSeek() { return regionSeek; } - /** - * {@inheritDoc} - */ @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/ahc-version.properties b/client/src/main/resources/org/asynchttpclient/config/ahc-version.properties similarity index 100% rename from api/src/main/resources/ahc-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 da1c721a8e..0000000000 --- a/extras/guava/pom.xml +++ /dev/null @@ -1,21 +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 125112a6a8..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 9e913af7f5..0000000000 --- a/extras/jdeferred/pom.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - 4.0.0 - - async-http-client-extras-parent - org.asynchttpclient - 2.0.0-SNAPSHOT - - async-http-client-extras-jdeferred - Asynchronous 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 ce4500799b..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 317f3d1672..0000000000 --- a/extras/jdeferred/src/test/java/org/asynchttpclient/extra/AsyncHttpTest.java +++ /dev/null @@ -1,100 +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(); - - try (AsyncHttpClient client = new DefaultAsyncHttpClient()) { - 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(); - } - } - - public void testMultiplePromiseAdapter() throws IOException { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicInteger successCount = new AtomicInteger(); - - try (AsyncHttpClient client = new DefaultAsyncHttpClient()) { - 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(); - } - } -} diff --git a/extras/pom.xml b/extras/pom.xml deleted file mode 100644 index c6a8fe636e..0000000000 --- a/extras/pom.xml +++ /dev/null @@ -1,64 +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 bfac0345e8..0000000000 --- a/extras/registry/pom.xml +++ /dev/null @@ -1,29 +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-netty3 - ${project.version} - test - - - org.asynchttpclient - async-http-client-netty4 - ${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 cb8076aecd..0000000000 --- a/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientFactory.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.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} - */ -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); - } - - 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 b0f4c2e0bd..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 a918bffdc1..0000000000 --- a/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncImplHelper.java +++ /dev/null @@ -1,64 +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.config.AsyncHttpClientConfigHelper; - -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; - -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"; - - /* - * 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 = AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getString(propertyName); - if (asyncHttpClientImplClassName != null) { - Class asyncHttpClientImplClass = AsyncImplHelper.getClass(asyncHttpClientImplClassName); - return asyncHttpClientImplClass; - } - return null; - } - - 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 c05e77099f..0000000000 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AbstractAsyncHttpClientFactoryTest.java +++ /dev/null @@ -1,250 +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.config.AsyncHttpClientConfigHelper; -import org.asynchttpclient.extras.registry.AsyncHttpClientFactory; -import org.asynchttpclient.extras.registry.AsyncHttpClientImplException; -import org.asynchttpclient.extras.registry.AsyncImplHelper; -import org.asynchttpclient.test.EchoHandler; -import org.asynchttpclient.test.TestUtils; -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); - AsyncHttpClientConfigHelper.reloadProperties(); - } - - @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. - * @throws Exception - */ - // ================================================================================================================ - @Test(groups = "fast") - public void testGetAsyncHttpClient() throws Exception { - try (AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient()) { - Assert.assertTrue(asyncHttpClient.getClass().equals(DefaultAsyncHttpClient.class)); - assertClientWorks(asyncHttpClient); - } - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientConfig() throws Exception { - try (AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(new AsyncHttpClientConfig.Builder().build())) { - Assert.assertTrue(asyncHttpClient.getClass().equals(DefaultAsyncHttpClient.class)); - assertClientWorks(asyncHttpClient); - } - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientProvider() throws Exception { - try (AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null))) { - Assert.assertTrue(asyncHttpClient.getClass().equals(DefaultAsyncHttpClient.class)); - assertClientWorks(asyncHttpClient); - } - } - - @Test(groups = "fast") - public void testGetAsyncHttpClientConfigAndProvider() throws Exception { - try (AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null), 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); - AsyncHttpClientConfigHelper.reloadProperties(); - 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); - AsyncHttpClientConfigHelper.reloadProperties(); - 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); - AsyncHttpClientConfigHelper.reloadProperties(); - 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); - AsyncHttpClientConfigHelper.reloadProperties(); - AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null), - 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); - AsyncHttpClientConfigHelper.reloadProperties(); - 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); - AsyncHttpClientConfigHelper.reloadProperties(); - 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); - AsyncHttpClientConfigHelper.reloadProperties(); - 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); - AsyncHttpClientConfigHelper.reloadProperties(); - try { - AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null), 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); - AsyncHttpClientConfigHelper.reloadProperties(); - 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); - AsyncHttpClientConfigHelper.reloadProperties(); - 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), - new AsyncHttpClientConfig.Builder().build()); - - } - - private void assertClientWorks(AsyncHttpClient asyncHttpClient) throws Exception { - Response response = asyncHttpClient.prepareGet("http://localhost/" + port + "/foo/test").execute().get(); - Assert.assertEquals(200, response.getStatusCode()); - } - - 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 47e6591037..0000000000 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryTest.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 org.asynchttpclient.config.AsyncHttpClientConfigHelper; -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); - AsyncHttpClientConfigHelper.reloadProperties(); - 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()); - AsyncHttpClientConfigHelper.reloadProperties(); - 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); - AsyncHttpClientConfigHelper.reloadProperties(); - 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); - AsyncHttpClientConfigHelper.reloadProperties(); - 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 0bf71df19f..0000000000 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClient.java +++ /dev/null @@ -1,137 +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; - -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) { - return null; - } - - @Override - public ListenableFuture executeRequest(Request request) { - 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/NettyAsyncHttpClientFactoryTest.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/NettyAsyncHttpClientFactoryTest.java deleted file mode 100644 index 0d57bc82cf..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.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 46d0095f89..0000000000 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/TestAsyncHttpClient.java +++ /dev/null @@ -1,130 +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; - -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) { - return null; - } - - @Override - public ListenableFuture executeRequest(Request request) { - 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 9fe76427f8..e55fe8a26b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,449 +1,459 @@ - - - org.sonatype.oss - oss-parent - 9 - - 4.0.0 - org.asynchttpclient - async-http-client-project - Asynchronous Http Client Project - 2.0.0-SNAPSHOT - pom - + + + 4.0.0 + + org.asynchttpclient + async-http-client-project + 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 - - 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 - - - - jira - https://issues.sonatype.org/browse/AHC - - - - asynchttpclient - http://groups.google.com/group/asynchttpclient/topics - - - http://groups.google.com/group/asynchttpclient/subscribe - - - http://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 - - - - - - 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 - - - install - - - maven-compiler-plugin - 3.3 - - ${source.property} - ${target.property} - 1024m - - - - - maven-surefire-plugin - 2.18.1 - - - ${surefire.redirectTestOutputToFile} - - - - - org.codehaus.mojo - animal-sniffer-maven-plugin - 1.14 - - - org.codehaus.mojo.signature - java17 - 1.0 - - - sun.misc.Unsafe - - - - - check-java-1.7-compat - process-classes - - check - - - - - - org.apache.felix - maven-bundle-plugin - 2.5.4 - true - - META-INF - - - $(replace;$(project.version);-SNAPSHOT;.$(tstamp;yyyyMMdd-HHmm)) - - Sonatype - - - - - osgi-bundle - package - - bundle - - - - - - maven-enforcer-plugin - 1.4 - - - enforce-versions - - enforce - - - - - 2.0.9 - - - 1.5 - - - - - - - - maven-resources-plugin - 2.7 - - UTF-8 - - - - maven-release-plugin - - - maven-jar-plugin - 2.6 - - - maven-source-plugin - 2.4 - - - attach-sources - verify - - jar-no-fork - - - - - - - - - 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} - - - - - maven.java.net - https://maven.java.net/content/repositories/releases - - - - api - providers - extras - - - - org.slf4j - slf4j-api - ${slf4j.version} - - - - ch.qos.logback - logback-classic - ${logback.version} - test - - - log4j - log4j - ${log4j.version} - test - - - org.testng - testng - ${testng.version} - test - - - org.beanshell - bsh - - - - - 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 - - - - -Xdoclint:none - http://oss.sonatype.org/content/repositories/snapshots - true - 1.7 - 1.7 - 1.7.12 - 1.1.3 - 1.2.17 - 6.8.8 - 9.2.11.v20150529 - 6.0.29 - 2.4 - 1.3 - 1.2.2 - - + 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 + 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/ + + + + + github + https://github.com/AsyncHttpClient/async-http-client/issues + + + + + asynchttpclient + https://groups.google.com/group/asynchttpclient/topics + https://groups.google.com/group/asynchttpclient/subscribe + https://groups.google.com/group/asynchttpclient/subscribe + asynchttpclient@googlegroups.com + + + + + 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} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.0 + + 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 + 3.5.2 + + + @{argLine} --add-exports java.base/jdk.internal.misc=ALL-UNNAMED + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.12 + + + + prepare-agent + + + + report + test + + report + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.11.1 + + + attach-javadocs + + jar + + + + + + + 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 + + + sign-artifacts + verify + + sign + + + + + --pinentry-mode + loopback + + false + + + + + + + com.github.siom79.japicmp + japicmp-maven-plugin + 0.23.1 + + + RELEASE + ${project.version} + + + true + true + true + false + public + + + + + + cmp + + verify + + + + + + diff --git a/providers/netty3/pom.xml b/providers/netty3/pom.xml deleted file mode 100644 index f59eeeb7c0..0000000000 --- a/providers/netty3/pom.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - org.asynchttpclient - async-http-client-providers-parent - 2.0.0-SNAPSHOT - - 4.0.0 - async-http-client-netty3 - Asynchronous Http Client Netty 3 Provider - - The Async Http Client Netty 3 Provider. - - - - - io.netty - netty - 3.10.4.Final - - - diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/Callback.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/Callback.java deleted file mode 100644 index a282ce2734..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/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.netty; - - -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/netty3/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProvider.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProvider.java deleted file mode 100644 index 6ed3c7c4a1..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProvider.java +++ /dev/null @@ -1,101 +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.netty; - -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.netty.channel.ChannelManager; -import org.asynchttpclient.netty.channel.pool.ChannelPoolPartitionSelector; -import org.asynchttpclient.netty.request.NettyRequestSender; -import org.asynchttpclient.netty.NettyAsyncHttpProvider; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig; -import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.util.HashedWheelTimer; -import org.jboss.netty.util.Timer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class NettyAsyncHttpProvider extends SimpleChannelUpstreamHandler implements AsyncHttpProvider { - - static final Logger LOGGER = LoggerFactory.getLogger(NettyAsyncHttpProvider.class); - - private final AsyncHttpClientConfig config; - private final AtomicBoolean closed = new AtomicBoolean(false); - private final ChannelManager channelManager; - private final boolean allowStopNettyTimer; - private final Timer nettyTimer; - - private final NettyRequestSender requestSender; - - public NettyAsyncHttpProvider(AsyncHttpClientConfig config) { - - this.config = config; - NettyAsyncHttpProviderConfig 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, channelManager, nettyTimer, closed); - channelManager.configureBootstraps(requestSender, closed); - } - - private Timer newNettyTimer() { - HashedWheelTimer timer = new HashedWheelTimer(); - timer.start(); - return timer; - } - - public void close() { - if (closed.compareAndSet(false, true)) { - try { - channelManager.close(); - - // FIXME shouldn't close if not allowed - config.executorService().shutdown(); - - if (allowStopNettyTimer) - nettyTimer.stop(); - - } catch (Throwable t) { - LOGGER.warn("Unexpected error on close", t); - } - } - } - - @Override - public ListenableFuture execute(Request request, final AsyncHandler asyncHandler) { - try { - return requestSender.sendRequest(request, asyncHandler, null, false); - } catch (Exception e) { - asyncHandler.onThrowable(e); - return new ListenableFuture.CompletedFailure<>(e); - } - } - - public void flushChannelPoolPartition(String partitionId) { - channelManager.flushPartition(partitionId); - } - - public void flushChannelPoolPartitions(ChannelPoolPartitionSelector selector) { - channelManager.flushPartitions(selector); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProviderConfig.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProviderConfig.java deleted file mode 100644 index b4cae8efcd..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProviderConfig.java +++ /dev/null @@ -1,212 +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.netty; - -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.channel.pool.ConnectionStrategy; -import org.asynchttpclient.netty.channel.pool.ChannelPool; -import org.asynchttpclient.netty.handler.DefaultConnectionStrategy; -import org.asynchttpclient.netty.ws.NettyWebSocket; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; -import org.jboss.netty.handler.codec.http.HttpRequest; -import org.jboss.netty.handler.codec.http.HttpResponse; -import org.jboss.netty.util.Timer; - -/** - * This class can be used to pass Netty's internal configuration options. See - * Netty documentation for more information. - */ -public class NettyAsyncHttpProviderConfig implements AsyncHttpProviderConfig { - - private final ConcurrentHashMap properties = new ConcurrentHashMap<>(); - - /** - * Add a property that will be used when the AsyncHttpClient initialize its - * {@link com.ning.http.client.AsyncHttpProvider} - * - * @param name the name of the property - * @param value the value of the property - * @return this instance of AsyncHttpProviderConfig - */ - public NettyAsyncHttpProviderConfig addProperty(String name, Object value) { - properties.put(name, value); - return this; - } - - /** - * Return the value associated with the property's name - * - * @param name - * @return this instance of AsyncHttpProviderConfig - */ - public Object getProperty(String name) { - return properties.get(name); - } - - /** - * Return the value associated with the property's name - * - * @param name - * @return this instance of AsyncHttpProviderConfig - */ - public T getProperty(String name, Class type, T defaultValue) { - Object value = properties.get(name); - if (value != null && type.isAssignableFrom(value.getClass())) { - return type.cast(value); - } - return defaultValue; - } - - /** - * Remove the value associated with the property's name - * - * @param name - * @return true if removed - */ - public Object removeProperty(String name) { - return properties.remove(name); - } - - /** - * Return the curent entry set. - * - * @return a the curent entry set. - */ - public Set> propertiesSet() { - return properties.entrySet(); - } - - /** - * Enable Netty DeadLockChecker - */ - private boolean useDeadLockChecker; - - /** - * Allow configuring the Netty's boss executor service. - */ - private ExecutorService bossExecutorService; - - private AdditionalPipelineInitializer httpAdditionalPipelineInitializer; - private AdditionalPipelineInitializer wsAdditionalPipelineInitializer; - - /** - * Allow configuring the Netty's socket channel factory. - */ - private NioClientSocketChannelFactory socketChannelFactory; - - private ChannelPool channelPool; - - private Timer nettyTimer; - - private NettyWebSocketFactory nettyWebSocketFactory = new DefaultNettyWebSocketFactory(); - - private ConnectionStrategy connectionStrategy = new DefaultConnectionStrategy(); - - public boolean isUseDeadLockChecker() { - return useDeadLockChecker; - } - - public void setUseDeadLockChecker(boolean useDeadLockChecker) { - this.useDeadLockChecker = useDeadLockChecker; - } - - public ExecutorService getBossExecutorService() { - return bossExecutorService; - } - - public void setBossExecutorService(ExecutorService bossExecutorService) { - this.bossExecutorService = bossExecutorService; - } - - public AdditionalPipelineInitializer getHttpAdditionalPipelineInitializer() { - return httpAdditionalPipelineInitializer; - } - - public void setHttpAdditionalPipelineInitializer(AdditionalPipelineInitializer httpAdditionalPipelineInitializer) { - this.httpAdditionalPipelineInitializer = httpAdditionalPipelineInitializer; - } - - public AdditionalPipelineInitializer getWsAdditionalPipelineInitializer() { - return wsAdditionalPipelineInitializer; - } - - public void setWsAdditionalPipelineInitializer(AdditionalPipelineInitializer wsAdditionalPipelineInitializer) { - this.wsAdditionalPipelineInitializer = wsAdditionalPipelineInitializer; - } - - public NioClientSocketChannelFactory getSocketChannelFactory() { - return socketChannelFactory; - } - - public void setSocketChannelFactory(NioClientSocketChannelFactory socketChannelFactory) { - this.socketChannelFactory = socketChannelFactory; - } - - public Timer getNettyTimer() { - return nettyTimer; - } - - public void setNettyTimer(Timer nettyTimer) { - this.nettyTimer = nettyTimer; - } - - public ChannelPool getChannelPool() { - return channelPool; - } - - public void setChannelPool(ChannelPool channelPool) { - this.channelPool = channelPool; - } - - public NettyWebSocketFactory getNettyWebSocketFactory() { - return nettyWebSocketFactory; - } - - public void setNettyWebSocketFactory(NettyWebSocketFactory nettyWebSocketFactory) { - this.nettyWebSocketFactory = nettyWebSocketFactory; - } - - public ConnectionStrategy getConnectionStrategy() { - return connectionStrategy; - } - - public void setConnectionStrategy(ConnectionStrategy connectionStrategy) { - this.connectionStrategy = connectionStrategy; - } - - public static interface NettyWebSocketFactory { - NettyWebSocket newNettyWebSocket(Channel channel, AsyncHttpClientConfig config); - } - - public static interface AdditionalPipelineInitializer { - - void initPipeline(ChannelPipeline pipeline) throws Exception; - } - - public class DefaultNettyWebSocketFactory implements NettyWebSocketFactory { - - @Override - public NettyWebSocket newNettyWebSocket(Channel channel, AsyncHttpClientConfig config) { - return new NettyWebSocket(channel, config); - } - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyResponse.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyResponse.java deleted file mode 100644 index d7da0155a3..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyResponse.java +++ /dev/null @@ -1,104 +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.netty; - -import static org.asynchttpclient.netty.util.ChannelBufferUtils.channelBuffer2bytes; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.ResponseBase; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.cookie.CookieDecoder; -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBufferInputStream; -import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.handler.codec.http.HttpHeaders; - -/** - * Wrapper around the {@link org.asynchttpclient.ning.http.client.Response} API. - */ -public class NettyResponse extends ResponseBase { - - public NettyResponse(HttpResponseStatus status, HttpResponseHeaders headers, List bodyParts) { - super(status, headers, bodyParts); - } - - @Override - public byte[] getResponseBodyAsBytes() throws IOException { - return channelBuffer2bytes(getResponseBodyAsChannelBuffer()); - } - - @Override - public ByteBuffer getResponseBodyAsByteBuffer() throws IOException { - return getResponseBodyAsChannelBuffer().toByteBuffer(); - } - - @Override - public String getResponseBody() throws IOException { - return getResponseBody(null); - } - - public String getResponseBody(Charset charset) throws IOException { - return getResponseBodyAsChannelBuffer().toString(calculateCharset(charset)); - } - - @Override - public InputStream getResponseBodyAsStream() throws IOException { - return new ChannelBufferInputStream(getResponseBodyAsChannelBuffer()); - } - - public ChannelBuffer getResponseBodyAsChannelBuffer() throws IOException { - ChannelBuffer b = null; - switch (bodyParts.size()) { - case 0: - b = ChannelBuffers.EMPTY_BUFFER; - break; - case 1: - b = NettyResponseBodyPart.class.cast(bodyParts.get(0)).getChannelBuffer(); - break; - default: - ChannelBuffer[] channelBuffers = new ChannelBuffer[bodyParts.size()]; - for (int i = 0; i < bodyParts.size(); i++) { - channelBuffers[i] = NettyResponseBodyPart.class.cast(bodyParts.get(i)).getChannelBuffer(); - } - b = ChannelBuffers.wrappedBuffer(channelBuffers); - } - - return b; - } - - @Override - protected List buildCookies() { - List cookies = new ArrayList<>(); - for (Map.Entry> header : headers.getHeaders().entrySet()) { - if (header.getKey().equalsIgnoreCase(HttpHeaders.Names.SET_COOKIE)) { - // TODO: ask for parsed header - List v = header.getValue(); - for (String value : v) { - cookies.add(CookieDecoder.decode(value)); - } - } - } - return cookies; - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyResponseBodyPart.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyResponseBodyPart.java deleted file mode 100644 index dc9111d06f..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyResponseBodyPart.java +++ /dev/null @@ -1,105 +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.netty; - -import static org.asynchttpclient.netty.util.ChannelBufferUtils.channelBuffer2bytes; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -import org.asynchttpclient.HttpResponseBodyPart; -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.handler.codec.http.HttpChunk; -import org.jboss.netty.handler.codec.http.HttpResponse; - -/** - * A callback class used when an HTTP response body is received. - */ -public class NettyResponseBodyPart extends HttpResponseBodyPart { - - private final boolean last; - private final ChannelBuffer content; - private volatile byte[] bytes; - private final int length; - private boolean closeConnection; - - public NettyResponseBodyPart(HttpResponse response, boolean last) { - this(response, null, last); - } - - public NettyResponseBodyPart(HttpResponse response, HttpChunk chunk, boolean last) { - this.last = last; - content = chunk != null ? chunk.getContent() : response.getContent(); - length = content.readableBytes(); - } - - /** - * Return the response body's part bytes received. - * - * @return the response body's part bytes received. - */ - public byte[] getBodyPartBytes() { - if (bytes == null) - bytes = channelBuffer2bytes(content); - return bytes; - } - - public int writeTo(OutputStream outputStream) throws IOException { - ChannelBuffer b = getChannelBuffer(); - int read = b.readableBytes(); - int index = b.readerIndex(); - if (read > 0) { - b.readBytes(outputStream, read); - } - b.readerIndex(index); - return read; - } - - public ChannelBuffer getChannelBuffer() { - return content; - } - - @Override - public ByteBuffer getBodyByteBuffer() { - return content.toByteBuffer(); - } - - @Override - public int length() { - return length; - } - - @Override - public InputStream readBodyPartBytes() { - return new ByteArrayInputStream(bytes); - } - - @Override - public boolean isLast() { - return last; - } - - @Override - public void markUnderlyingConnectionAsToBeClosed() { - closeConnection = true; - } - - @Override - public boolean isUnderlyingConnectionToBeClosed() { - return closeConnection; - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java deleted file mode 100644 index eeac423b6e..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java +++ /dev/null @@ -1,445 +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.netty; - -import static org.asynchttpclient.util.DateUtils.millisTime; - -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.Request; -import org.asynchttpclient.channel.pool.ConnectionPoolPartitioning; -import org.asynchttpclient.future.AbstractListenableFuture; -import org.asynchttpclient.netty.channel.Channels; -import org.asynchttpclient.netty.request.NettyRequest; -import org.asynchttpclient.netty.timeout.TimeoutsHolder; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.uri.Uri; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.handler.codec.http.HttpHeaders; -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 final long start = millisTime(); - private final ConnectionPoolPartitioning connectionPoolPartitioning; - 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 boolean keepAlive = true; - private Request request; - private NettyRequest nettyRequest; - private HttpHeaders httpHeaders; - private AsyncHandler asyncHandler; - private boolean streamWasAlreadyConsumed; - private boolean reuseChannel; - private boolean headersAlreadyWrittenOnContinue; - private boolean dontWriteBodyBecauseExpectContinue; - private boolean allowConnect; - - public NettyResponseFuture(Request request,// - AsyncHandler asyncHandler,// - NettyRequest nettyRequest,// - int maxRetry,// - ConnectionPoolPartitioning connectionPoolPartitioning,// - ProxyServer proxyServer) { - - this.asyncHandler = asyncHandler; - this.request = request; - this.nettyRequest = nettyRequest; - this.connectionPoolPartitioning = connectionPoolPartitioning; - this.proxyServer = proxyServer; - this.maxRetry = maxRetry; - } - - /*********************************************/ - /** java.util.concurrent.Future **/ - /*********************************************/ - - @Override - public boolean isDone() { - return isDone.get() || isCancelled(); - } - - @Override - public boolean isCancelled() { - return isCancelled.get(); - } - - @Override - public boolean cancel(boolean force) { - cancelTimeouts(); - - if (isCancelled.getAndSet(true)) - return false; - - // cancel could happen before channel was attached - if (channel != null) { - Channels.setDiscard(channel); - Channels.silentlyCloseChannel(channel); - } - - 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 { - - if (isCancelled()) - throw new CancellationException(); - - 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 **/ - /*********************************************/ - - private boolean terminateAndExit() { - cancelTimeouts(); - this.channel = null; - this.reuseChannel = false; - return isDone.getAndSet(true) || isCancelled.get(); - } - - public final void done() { - - if (terminateAndExit()) - 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) { - - exEx.compareAndSet(null, new ExecutionException(t)); - - if (terminateAndExit()) - return; - - 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 Uri getUri() { - return request.getUri(); - } - - public ConnectionPoolPartitioning getConnectionPoolPartitioning() { - return connectionPoolPartitioning; - } - - public ProxyServer getProxyServer() { - return proxyServer; - } - - public void setAsyncHandler(AsyncHandler asyncHandler) { - this.asyncHandler = asyncHandler; - } - - 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 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) { - - // future could have been cancelled first - if (isDone()) { - Channels.silentlyCloseChannel(channel); - } - - this.channel = channel; - this.reuseChannel = reuseChannel; - } - - public Channel channel() { - return channel; - } - - public boolean reuseChannel() { - return reuseChannel; - } - - public boolean canRetry() { - return maxRetry > 0 && currentRetry.incrementAndGet() <= maxRetry; - } - - public SocketAddress getChannelRemoteAddress() { - return channel != null ? channel.getRemoteAddress() : 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) && !getUri().getScheme().equalsIgnoreCase("https")) && !isInAuth(); - } - - public long getStart() { - return start; - } - - public Object getPartitionKey() { - return connectionPoolPartitioning.getPartitionKey(request.getUri(), request.getVirtualHost(), proxyServer); - } - - @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=" + getUri() + // - ",\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/netty3/src/main/java/org/asynchttpclient/netty/NettyResponseHeaders.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyResponseHeaders.java deleted file mode 100644 index a70ca63743..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyResponseHeaders.java +++ /dev/null @@ -1,67 +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.netty; - -import java.util.Map; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseHeaders; -import org.jboss.netty.handler.codec.http.HttpHeaders; - -/** - * 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/netty3/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java deleted file mode 100644 index b298172634..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java +++ /dev/null @@ -1,101 +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.netty; - -import java.net.SocketAddress; -import java.util.List; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; -import org.asynchttpclient.uri.Uri; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.handler.codec.http.HttpResponse; - -/** - * 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, AsyncHttpClientConfig config, HttpResponse response, Channel channel) { - super(uri, config); - this.response = response; - if (channel != null) { - remoteAddress = channel.getRemoteAddress(); - localAddress = channel.getLocalAddress(); - } else { - remoteAddress = null; - localAddress = null; - } - } - - /** - * Return the response status code - * - * @return the response status code - */ - public int getStatusCode() { - return response.getStatus().getCode(); - } - - /** - * Return the response status text - * - * @return the response status text - */ - public String getStatusText() { - return response.getStatus().getReasonPhrase(); - } - - @Override - public String getProtocolName() { - return response.getProtocolVersion().getProtocolName(); - } - - @Override - public int getProtocolMajorVersion() { - return response.getProtocolVersion().getMajorVersion(); - } - - @Override - public int getProtocolMinorVersion() { - return response.getProtocolVersion().getMinorVersion(); - } - - @Override - public String getProtocolText() { - return response.getProtocolVersion().getText(); - } - - @Override - public Response prepareResponse(HttpResponseHeaders headers, List bodyParts) { - return new NettyResponse(this, headers, bodyParts); - } - - @Override - public SocketAddress getRemoteAddress() { - return remoteAddress; - } - - @Override - public SocketAddress getLocalAddress() { - return localAddress; - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java deleted file mode 100644 index 0272e941ce..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java +++ /dev/null @@ -1,470 +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.netty.channel; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getExplicitPort; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getSchemeDefaultPort; -import static org.asynchttpclient.util.HttpUtils.WS; -import static org.asynchttpclient.util.HttpUtils.isSecure; -import static org.asynchttpclient.util.HttpUtils.isWebSocket; -import static org.asynchttpclient.util.MiscUtils.buildStaticIOException; -import static org.jboss.netty.channel.Channels.pipeline; -import static org.jboss.netty.handler.ssl.SslHandler.getDefaultBufferPool; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.Map.Entry; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.net.ssl.SSLEngine; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.channel.SSLEngineFactory; -import org.asynchttpclient.channel.pool.ConnectionPoolPartitioning; -import org.asynchttpclient.handler.AsyncHandlerExtensions; -import org.asynchttpclient.internal.chmv8.ConcurrentHashMapV8; -import org.asynchttpclient.netty.Callback; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.pool.ChannelPool; -import org.asynchttpclient.netty.channel.pool.ChannelPoolPartitionSelector; -import org.asynchttpclient.netty.channel.pool.DefaultChannelPool; -import org.asynchttpclient.netty.channel.pool.NoopChannelPool; -import org.asynchttpclient.netty.handler.HttpProtocol; -import org.asynchttpclient.netty.handler.Processor; -import org.asynchttpclient.netty.handler.Protocol; -import org.asynchttpclient.netty.handler.WebSocketProtocol; -import org.asynchttpclient.netty.request.NettyRequestSender; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.uri.Uri; -import org.jboss.netty.bootstrap.ClientBootstrap; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.channel.DefaultChannelFuture; -import org.jboss.netty.channel.group.ChannelGroup; -import org.jboss.netty.channel.socket.ClientSocketChannelFactory; -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; -import org.jboss.netty.handler.codec.http.HttpClientCodec; -import org.jboss.netty.handler.codec.http.HttpContentDecompressor; -import org.jboss.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder; -import org.jboss.netty.handler.codec.http.websocketx.WebSocket08FrameEncoder; -import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrameAggregator; -import org.jboss.netty.handler.ssl.SslHandler; -import org.jboss.netty.handler.stream.ChunkedWriteHandler; -import org.jboss.netty.util.Timer; -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 SSLEngineFactory sslEngineFactory; - private final ChannelPool channelPool; - private final boolean maxTotalConnectionsEnabled; - private final Semaphore freeChannels; - private final ChannelGroup openChannels; - private final boolean maxConnectionsPerHostEnabled; - private final ConcurrentHashMapV8 freeChannelsPerHost; - private final ConcurrentHashMapV8 channelId2PartitionKey; - private final long handshakeTimeout; - private final Timer nettyTimer; - private final IOException tooManyConnections; - private final IOException tooManyConnectionsPerHost; - private final IOException poolAlreadyClosed; - - private final ClientSocketChannelFactory socketChannelFactory; - private final boolean allowReleaseSocketChannelFactory; - private final ClientBootstrap httpBootstrap; - private final ClientBootstrap wsBootstrap; - private final ConcurrentHashMapV8.Fun semaphoreComputer; - - private Processor wsProcessor; - - public ChannelManager(final AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, Timer nettyTimer) { - - this.config = config; - this.nettyConfig = nettyConfig; - this.nettyTimer = nettyTimer; - this.sslEngineFactory = config.getSslEngineFactory() != null ? config.getSslEngineFactory() : new SSLEngineFactory.DefaultSSLEngineFactory(config); - - ChannelPool channelPool = nettyConfig.getChannelPool(); - if (channelPool == null && config.isAllowPoolingConnections()) { - channelPool = new DefaultChannelPool(config, nettyTimer); - } else if (channelPool == null) { - channelPool = new NoopChannelPool(); - } - this.channelPool = channelPool; - - tooManyConnections = buildStaticIOException(String.format("Too many connections %s", config.getMaxConnections())); - tooManyConnectionsPerHost = buildStaticIOException(String.format("Too many connections per host %s", config.getMaxConnectionsPerHost())); - poolAlreadyClosed = buildStaticIOException("Pool is already closed"); - maxTotalConnectionsEnabled = config.getMaxConnections() > 0; - maxConnectionsPerHostEnabled = config.getMaxConnectionsPerHost() > 0; - - if (maxTotalConnectionsEnabled || maxConnectionsPerHostEnabled) { - openChannels = new CleanupChannelGroup("asyncHttpClient") { - @Override - public boolean remove(Object o) { - boolean removed = super.remove(o); - if (removed) { - if (maxTotalConnectionsEnabled) - freeChannels.release(); - if (maxConnectionsPerHostEnabled) { - Object partitionKey = channelId2PartitionKey.remove(Channel.class.cast(o).getId()); - if (partitionKey != null) { - Semaphore freeChannelsForHost = freeChannelsPerHost.get(partitionKey); - if (freeChannelsForHost != null) - freeChannelsForHost.release(); - } - } - } - return removed; - } - }; - freeChannels = new Semaphore(config.getMaxConnections()); - } else { - openChannels = new CleanupChannelGroup("asyncHttpClient"); - freeChannels = null; - } - - if (maxConnectionsPerHostEnabled) { - freeChannelsPerHost = new ConcurrentHashMapV8<>(); - channelId2PartitionKey = new ConcurrentHashMapV8<>(); - semaphoreComputer = new ConcurrentHashMapV8.Fun() { - @Override - public Semaphore apply(Object partitionKey) { - return new Semaphore(config.getMaxConnectionsPerHost()); - } - }; - } else { - freeChannelsPerHost = null; - channelId2PartitionKey = null; - semaphoreComputer = null; - } - - handshakeTimeout = config.getHandshakeTimeout(); - - if (nettyConfig.getSocketChannelFactory() != null) { - socketChannelFactory = nettyConfig.getSocketChannelFactory(); - // cannot allow releasing shared channel factory - allowReleaseSocketChannelFactory = false; - - } else { - ExecutorService e = nettyConfig.getBossExecutorService(); - if (e == null) - e = Executors.newCachedThreadPool(); - int numWorkers = config.getIoThreadMultiplier() * Runtime.getRuntime().availableProcessors(); - LOGGER.trace("Number of application's worker threads is {}", numWorkers); - socketChannelFactory = new NioClientSocketChannelFactory(e, config.executorService(), numWorkers); - allowReleaseSocketChannelFactory = true; - } - - httpBootstrap = new ClientBootstrap(socketChannelFactory); - wsBootstrap = new ClientBootstrap(socketChannelFactory); - - DefaultChannelFuture.setUseDeadLockChecker(nettyConfig.isUseDeadLockChecker()); - - // FIXME isn't there a constant for this name??? - if (config.getConnectTimeout() > 0) - nettyConfig.addProperty("connectTimeoutMillis", config.getConnectTimeout()); - for (Entry entry : nettyConfig.propertiesSet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - httpBootstrap.setOption(key, value); - wsBootstrap.setOption(key, value); - } - } - - public void configureBootstraps(NettyRequestSender requestSender, AtomicBoolean closed) { - - Protocol httpProtocol = new HttpProtocol(this, config, nettyConfig, requestSender); - final Processor httpProcessor = new Processor(config, this, requestSender, httpProtocol); - - Protocol wsProtocol = new WebSocketProtocol(this, config, nettyConfig, requestSender); - wsProcessor = new Processor(config, this, requestSender, wsProtocol); - - httpBootstrap.setPipelineFactory(new ChannelPipelineFactory() { - - public ChannelPipeline getPipeline() throws Exception { - ChannelPipeline pipeline = pipeline(); - pipeline.addLast(HTTP_HANDLER, newHttpClientCodec()); - pipeline.addLast(INFLATER_HANDLER, newHttpContentDecompressor()); - pipeline.addLast(CHUNKED_WRITER_HANDLER, new ChunkedWriteHandler()); - pipeline.addLast(HTTP_PROCESSOR, httpProcessor); - - if (nettyConfig.getHttpAdditionalPipelineInitializer() != null) - nettyConfig.getHttpAdditionalPipelineInitializer().initPipeline(pipeline); - - return pipeline; - } - }); - - wsBootstrap.setPipelineFactory(new ChannelPipelineFactory() { - - public ChannelPipeline getPipeline() throws Exception { - ChannelPipeline pipeline = pipeline(); - pipeline.addLast(HTTP_HANDLER, newHttpClientCodec()); - pipeline.addLast(WS_PROCESSOR, wsProcessor); - - if (nettyConfig.getWsAdditionalPipelineInitializer() != null) - nettyConfig.getWsAdditionalPipelineInitializer().initPipeline(pipeline); - - return pipeline; - } - }); - } - - private HttpContentDecompressor newHttpContentDecompressor() { - if (config.isKeepEncodingHeader()) - return new HttpContentDecompressor() { - @Override - protected String getTargetContentEncoding(String contentEncoding) throws Exception { - return contentEncoding; - } - }; - else - return new HttpContentDecompressor(); - } - - public final void tryToOfferChannelToPool(Channel channel, AsyncHandler handler, boolean keepAlive, Object partitionKey) { - if (channel.isConnected() && keepAlive && channel.isReadable()) { - LOGGER.debug("Adding key: {} for channel {}", partitionKey, channel); - Channels.setDiscard(channel); - if (handler instanceof AsyncHandlerExtensions) { - AsyncHandlerExtensions.class.cast(handler).onConnectionOffer(channel); - } - channelPool.offer(channel, partitionKey); - if (maxConnectionsPerHostEnabled) - channelId2PartitionKey.putIfAbsent(channel.getId(), partitionKey); - } else { - // not offered - closeChannel(channel); - } - } - - public Channel poll(Uri uri, String virtualHost, ProxyServer proxy, ConnectionPoolPartitioning connectionPoolPartitioning) { - Object partitionKey = connectionPoolPartitioning.getPartitionKey(uri, virtualHost, proxy); - return channelPool.poll(partitionKey); - } - - public boolean removeAll(Channel connection) { - return channelPool.removeAll(connection); - } - - private boolean tryAcquireGlobal() { - return !maxTotalConnectionsEnabled || freeChannels.tryAcquire(); - } - - private Semaphore getFreeConnectionsForHost(Object partitionKey) { - return freeChannelsPerHost.computeIfAbsent(partitionKey, semaphoreComputer); - } - - private boolean tryAcquirePerHost(Object partitionKey) { - return !maxConnectionsPerHostEnabled || getFreeConnectionsForHost(partitionKey).tryAcquire(); - } - - public void preemptChannel(Object partitionKey) throws IOException { - if (!channelPool.isOpen()) - throw poolAlreadyClosed; - if (!tryAcquireGlobal()) - throw tooManyConnections; - if (!tryAcquirePerHost(partitionKey)) { - if (maxTotalConnectionsEnabled) - freeChannels.release(); - - throw tooManyConnectionsPerHost; - } - } - - 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(); - } - } - - // FIXME also shutdown in provider - config.executorService().shutdown(); - if (allowReleaseSocketChannelFactory) { - socketChannelFactory.releaseExternalResources(); - httpBootstrap.releaseExternalResources(); - wsBootstrap.releaseExternalResources(); - } - } - - public void closeChannel(Channel channel) { - - // The channel may have already been removed from the future if a - // timeout occurred, and this method may be called just after. - LOGGER.debug("Closing Channel {} ", channel); - try { - removeAll(channel); - Channels.setDiscard(channel); - Channels.silentlyCloseChannel(channel); - } catch (Throwable t) { - LOGGER.debug("Error closing a connection", t); - } - openChannels.remove(channel); - } - - public void abortChannelPreemption(Object partitionKey) { - if (maxTotalConnectionsEnabled) - freeChannels.release(); - if (maxConnectionsPerHostEnabled) - getFreeConnectionsForHost(partitionKey).release(); - } - - public void registerOpenChannel(Channel channel, Object partitionKey) { - openChannels.add(channel); - if (maxConnectionsPerHostEnabled) { - channelId2PartitionKey.put(channel.getId(), partitionKey); - } - } - - private HttpClientCodec newHttpClientCodec() { - return new HttpClientCodec(// - config.getHttpClientCodecMaxInitialLineLength(),// - config.getHttpClientCodecMaxHeaderSize(),// - config.getHttpClientCodecMaxChunkSize()); - } - - private SslHandler createSslHandler(String peerHost, int peerPort) throws GeneralSecurityException { - SSLEngine sslEngine = sslEngineFactory.newSSLEngine(peerHost, peerPort); - SslHandler sslHandler = handshakeTimeout > 0 ? new SslHandler(sslEngine, getDefaultBufferPool(), false, nettyTimer, handshakeTimeout) : new SslHandler(sslEngine); - sslHandler.setCloseOnSSLException(true); - return sslHandler; - } - - public static boolean isSslHandlerConfigured(ChannelPipeline pipeline) { - return pipeline.get(SSL_HANDLER) != null; - } - - public void upgradeProtocol(ChannelPipeline pipeline, String scheme, String host, int port) throws 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 SslHandler addSslHandler(ChannelPipeline pipeline, Uri uri, String virtualHost) throws GeneralSecurityException { - String peerHost; - int peerPort; - - if (virtualHost != null) { - int i = virtualHost.indexOf(':'); - if (i == -1) { - peerHost = virtualHost; - peerPort = getSchemeDefaultPort(uri.getScheme()); - } else { - peerHost = virtualHost.substring(0, i); - peerPort = Integer.valueOf(virtualHost.substring(i + 1)); - } - - } else { - peerHost = uri.getHost(); - peerPort = getExplicitPort(uri); - } - - SslHandler sslHandler = createSslHandler(peerHost, peerPort); - pipeline.addFirst(SSL_HANDLER, sslHandler); - return sslHandler; - } - - public void verifyChannelPipeline(ChannelPipeline pipeline, Uri uri, String virtualHost) throws GeneralSecurityException { - - boolean sslHandlerConfigured = isSslHandlerConfigured(pipeline); - - if (isSecure(uri.getScheme())) { - if (!sslHandlerConfigured) { - addSslHandler(pipeline, uri, virtualHost); - } - - } else if (sslHandlerConfigured) - pipeline.remove(SSL_HANDLER); - } - - public ClientBootstrap getBootstrap(String scheme, boolean useProxy) { - return scheme.startsWith(WS) && !useProxy ? wsBootstrap : httpBootstrap; - } - - 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, config.getWebSocketMaxFrameSize())); - pipeline.addAfter(WS_DECODER_HANDLER, WS_FRAME_AGGREGATOR, new WebSocketFrameAggregator(config.getWebSocketMaxBufferSize())); - } - - public final Callback newDrainCallback(final NettyResponseFuture future, final Channel channel, final boolean keepAlive, final Object partitionKey) { - - return new Callback(future) { - @Override - public void call() { - tryToOfferChannelToPool(channel, future.getAsyncHandler(), keepAlive, partitionKey); - } - }; - } - - public void drainChannelAndOffer(final Channel channel, final NettyResponseFuture future) { - drainChannelAndOffer(channel, future, future.isKeepAlive(), future.getPartitionKey()); - } - - public void drainChannelAndOffer(final Channel channel, final NettyResponseFuture future, boolean keepAlive, Object partitionKey) { - Channels.setAttribute(channel, newDrainCallback(future, channel, keepAlive, partitionKey)); - } - - public void flushPartition(Object partitionKey) { - channelPool.flushPartition(partitionKey); - } - - public void flushPartitions(ChannelPoolPartitionSelector selector) { - channelPool.flushPartitions(selector); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/Channels.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/Channels.java deleted file mode 100644 index 28cb05b6dc..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/Channels.java +++ /dev/null @@ -1,52 +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.netty.channel; - -import org.asynchttpclient.netty.DiscardEvent; -import org.jboss.netty.channel.Channel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class Channels { - - private static final Logger LOGGER = LoggerFactory.getLogger(Channels.class); - - private Channels() { - } - - public static void setAttribute(Channel channel, Object attribute) { - channel.setAttachment(attribute); - } - - public static Object getAttribute(Channel channel) { - return channel.getAttachment(); - } - - public static void setDiscard(Channel channel) { - setAttribute(channel, DiscardEvent.INSTANCE); - } - - public static boolean isChannelValid(Channel channel) { - return channel != null && channel.isConnected(); - } - - public static void silentlyCloseChannel(Channel channel) { - try { - if (channel != null && channel.isOpen()) - channel.close(); - } catch (Throwable t) { - LOGGER.debug("Failed to close channel", t); - } - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/CleanupChannelGroup.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/CleanupChannelGroup.java deleted file mode 100644 index 66a83e9dd5..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/CleanupChannelGroup.java +++ /dev/null @@ -1,95 +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.netty.channel; - -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.group.ChannelGroup; -import org.jboss.netty.channel.group.ChannelGroupFuture; -import org.jboss.netty.channel.group.DefaultChannelGroup; -import org.jboss.netty.channel.group.DefaultChannelGroupFuture; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Collection; -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 { - - private final static Logger logger = LoggerFactory.getLogger(CleanupChannelGroup.class); - - // internal vars -------------------------------------------------------------------------------------------------- - - private final AtomicBoolean closed; - private final ReentrantReadWriteLock lock; - - // constructors --------------------------------------------------------------------------------------------------- - - public CleanupChannelGroup() { - this.closed = new AtomicBoolean(false); - this.lock = new ReentrantReadWriteLock(); - } - - public CleanupChannelGroup(String name) { - super(name); - this.closed = new AtomicBoolean(false); - this.lock = new ReentrantReadWriteLock(); - } - - // DefaultChannelGroup -------------------------------------------------------------------------------------------- - - @Override - public ChannelGroupFuture close() { - this.lock.writeLock().lock(); - try { - if (!this.closed.getAndSet(true)) { - // First time close() is called. - return super.close(); - } else { - Collection futures = new ArrayList<>(); - logger.debug("CleanupChannelGroup Already closed"); - return new DefaultChannelGroupFuture(ChannelGroup.class.cast(this), futures); - } - } 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. - Channels.silentlyCloseChannel(channel); - return false; - } - - return super.add(channel); - } finally { - this.lock.readLock().unlock(); - } - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java deleted file mode 100644 index cf313fe9cd..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java +++ /dev/null @@ -1,150 +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.netty.channel; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getBaseUrl; -import static org.asynchttpclient.util.HttpUtils.isSecure; - -import java.net.ConnectException; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.Request; -import org.asynchttpclient.handler.AsyncHandlerExtensions; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.future.StackTraceInspector; -import org.asynchttpclient.netty.request.NettyRequestSender; -import org.asynchttpclient.uri.Uri; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.ChannelFutureListener; -import org.jboss.netty.handler.ssl.SslHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Non Blocking connect. - */ -public final class NettyConnectListener implements ChannelFutureListener { - private static final Logger LOGGER = LoggerFactory.getLogger(NettyConnectListener.class); - private final NettyResponseFuture future; - private final NettyRequestSender requestSender; - private final ChannelManager channelManager; - private final boolean channelPreempted; - private final Object partitionKey; - - public NettyConnectListener(NettyResponseFuture future,// - NettyRequestSender requestSender,// - ChannelManager channelManager,// - boolean channelPreempted,// - Object partitionKey) { - this.future = future; - this.requestSender = requestSender; - this.channelManager = channelManager; - this.channelPreempted = channelPreempted; - this.partitionKey = partitionKey; - } - - public NettyResponseFuture future() { - return future; - } - - private void abortChannelPreemption() { - if (channelPreempted) - channelManager.abortChannelPreemption(partitionKey); - } - - private void writeRequest(Channel channel) { - - LOGGER.debug("Using non-cached Channel {} for {} '{}'", - channel, - future.getNettyRequest().getHttpRequest().getMethod(), - future.getNettyRequest().getHttpRequest().getUri()); - - Channels.setAttribute(channel, future); - - if (future.isDone()) { - abortChannelPreemption(); - return; - } - - future.attachChannel(channel, false); - - if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onConnectionOpened(channel); - - channelManager.registerOpenChannel(channel, partitionKey); - requestSender.writeRequest(future, channel); - } - - private void onFutureSuccess(final Channel channel) throws Exception { - - Request request = future.getRequest(); - Uri uri = request.getUri(); - - // in case of proxy tunneling, we'll add the SslHandler later, after the CONNECT request - if (future.getProxyServer() == null && isSecure(uri)) { - SslHandler sslHandler = channelManager.addSslHandler(channel.getPipeline(), uri, request.getVirtualHost()); - sslHandler.handshake().addListener(new ChannelFutureListener() { - - @Override - public void operationComplete(ChannelFuture handshakeFuture) throws Exception { - if (handshakeFuture.isSuccess()) { - final AsyncHandler asyncHandler = future.getAsyncHandler(); - if (asyncHandler instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(asyncHandler).onSslHandshakeCompleted(); - - writeRequest(channel); - } else { - onFutureFailure(channel, handshakeFuture.getCause()); - } - } - }); - - } else { - writeRequest(channel); - } - } - - private void onFutureFailure(Channel channel, Throwable cause) { - abortChannelPreemption(); - - boolean canRetry = future.canRetry(); - LOGGER.debug("Trying to recover from failing to connect channel {} with a retry value of {} ", channel, canRetry); - if (canRetry - && cause != null - && (future.getState() != NettyResponseFuture.STATE.NEW || StackTraceInspector.recoverOnNettyDisconnectException(cause))) { - - if (requestSender.retry(future)) - return; - } - - LOGGER.debug("Failed to recover from connect exception: {} with channel {}", cause, channel); - - boolean printCause = cause != null && cause.getMessage() != null; - String printedCause = printCause ? cause.getMessage() : getBaseUrl(future.getUri()); - ConnectException e = new ConnectException(printedCause); - if (cause != null) { - e.initCause(cause); - } - future.abort(e); - } - - public final void operationComplete(ChannelFuture f) throws Exception { - Channel channel = f.getChannel(); - if (f.isSuccess()) - onFutureSuccess(channel); - else - onFutureFailure(channel, f.getCause()); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/pool/ChannelPool.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/pool/ChannelPool.java deleted file mode 100644 index ef423102a7..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/pool/ChannelPool.java +++ /dev/null @@ -1,76 +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.netty.channel.pool; - -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.netty.channel.pool.ChannelPoolPartitionSelector; -import org.jboss.netty.channel.Channel; - -/** - * An interface used by an {@link AsyncHttpProvider} for caching http connections. - */ -public interface ChannelPool { - - /** - * Add a connection to the pool - * - * @param partitionKey a key used to retrieve the cached connection - * @param connection an I/O connection - * @return true if added. - */ - boolean offer(Channel connection, Object partitionKey); - - /** - * Get a connection from a partition - * - * @param partitionKey the id of the partition used when invoking offer - * @return the connection associated with the partitionId - */ - Channel poll(Object partitionKey); - - /** - * Remove all connections from the cache. A connection might have been associated with several uri. - * - * @param connection a connection - * @return the true if the connection has been removed - */ - boolean removeAll(Channel connection); - - /** - * Return true if a connection 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 connection can be cached. - */ - boolean isOpen(); - - /** - * Destroy all connections that has been cached by this instance. - */ - void destroy(); - - /** - * Flush a partition - * - * @param partitionKey - */ - void flushPartition(Object partitionKey); - - /** - * Flush partitions based on a selector - * - * @param selector - */ - void flushPartitions(ChannelPoolPartitionSelector selector); -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/pool/DefaultChannelPool.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/pool/DefaultChannelPool.java deleted file mode 100644 index 699535c713..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/pool/DefaultChannelPool.java +++ /dev/null @@ -1,329 +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.netty.channel.pool; - -import static org.asynchttpclient.util.DateUtils.millisTime; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.internal.chmv8.ConcurrentHashMapV8; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.Channels; -import org.asynchttpclient.netty.channel.pool.ChannelPoolPartitionSelector; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.handler.ssl.SslHandler; -import org.jboss.netty.util.Timeout; -import org.jboss.netty.util.Timer; -import org.jboss.netty.util.TimerTask; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A simple implementation of {@link com.ning.http.client.providers.netty.channel.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 static final ConcurrentHashMapV8.Fun> PARTITION_COMPUTER = new ConcurrentHashMapV8.Fun>() { - @Override - public ConcurrentLinkedQueue apply(Object partitionKey) { - return new ConcurrentLinkedQueue<>(); - } - }; - - private final ConcurrentHashMapV8> partitions = new ConcurrentHashMapV8<>(); - private final ConcurrentHashMapV8 channelId2Creation = new ConcurrentHashMapV8<>(); - 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 Object partitionKey; - - ChannelCreation(long creationTime, Object partitionKey) { - this.creationTime = creationTime; - this.partitionKey = partitionKey; - } - } - - 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 = channelId2Creation.get(channel.getId()); - return creation != null && now - creation.creationTime >= maxConnectionTTL; - } - - private final class IdleChannelDetector implements TimerTask { - - private boolean isIdleTimeoutExpired(IdleChannel idleChannel, long now) { - return !maxIdleTimeDisabled && now - idleChannel.start >= maxIdleTime; - } - - private List expiredChannels(ConcurrentLinkedQueue partition, long now) { - // lazy create - List idleTimeoutChannels = null; - for (IdleChannel idleChannel : partition) { - if (isTTLExpired(idleChannel.channel, now) || isIdleTimeoutExpired(idleChannel, now) - || !Channels.isChannelValid(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 false; - } - } - 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)) { - 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; - } - - public void run(Timeout timeout) throws Exception { - - if (isClosed.get()) - return; - - try { - if (LOGGER.isDebugEnabled()) { - for (Object key : partitions.keySet()) { - LOGGER.debug("Entry count for : {} : {}", key, partitions.get(key).size()); - } - } - - - long start = millisTime(); - int closedCount = 0; - int totalCount = 0; - - for (ConcurrentLinkedQueue partition : partitions.values()) { - - // store in intermediate unsynchronized lists to minimize the impact on the ConcurrentLinkedQueue - if (LOGGER.isDebugEnabled()) - totalCount += partition.size(); - - List closedChannels = closeChannels(expiredChannels(partition, start)); - - if (!closedChannels.isEmpty()) { - for (IdleChannel closedChannel : closedChannels) - channelId2Creation.remove(closedChannel.channel.getId()); - - partition.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.getTask()); - } - } - - public boolean offer(Channel channel, Object partitionKey) { - if (isClosed.get() || (!sslConnectionPoolEnabled && channel.getPipeline().get(SslHandler.class) != null)) - return false; - - long now = millisTime(); - - if (isTTLExpired(channel, now)) - return false; - - boolean added = partitions.computeIfAbsent(partitionKey, PARTITION_COMPUTER).add(new IdleChannel(channel, now)); - if (added) - channelId2Creation.putIfAbsent(channel.getId(), new ChannelCreation(now, partitionKey)); - - return added; - } - - public Channel poll(Object partitionKey) { - - IdleChannel idleChannel = null; - ConcurrentLinkedQueue partition = partitions.get(partitionKey); - if (partition != null) { - while (idleChannel == null) { - idleChannel = partition.poll(); - - if (idleChannel == null) - // pool is empty - break; - else if (!Channels.isChannelValid(idleChannel.channel)) { - idleChannel = null; - LOGGER.trace("Channel not connected or not opened, probably remotely closed!"); - } - } - } - return idleChannel != null ? idleChannel.channel : null; - } - - @Override - public boolean removeAll(Channel channel) { - ChannelCreation creation = channelId2Creation.remove(channel.getId()); - return !isClosed.get() && creation != null && partitions.get(creation.partitionKey).remove(channel); - } - - @Override - public boolean isOpen() { - return !isClosed.get(); - } - - @Override - public void destroy() { - if (isClosed.getAndSet(true)) - return; - - for (ConcurrentLinkedQueue partition : partitions.values()) { - for (IdleChannel idleChannel : partition) - close(idleChannel.channel); - } - - partitions.clear(); - channelId2Creation.clear(); - } - - private void close(Channel channel) { - // FIXME pity to have to do this here - Channels.setDiscard(channel); - channelId2Creation.remove(channel.getId()); - Channels.silentlyCloseChannel(channel); - } - - private void flushPartition(Object partitionKey, ConcurrentLinkedQueue partition) { - if (partition != null) { - partitions.remove(partitionKey); - for (IdleChannel idleChannel : partition) - close(idleChannel.channel); - } - } - - @Override - public void flushPartition(Object partitionKey) { - flushPartition(partitionKey, partitions.get(partitionKey)); - } - - @Override - public void flushPartitions(ChannelPoolPartitionSelector selector) { - - for (Map.Entry> partitionsEntry : partitions.entrySet()) { - Object partitionKey = partitionsEntry.getKey(); - if (selector.select(partitionKey)) - flushPartition(partitionKey, partitionsEntry.getValue()); - } - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/pool/NoopChannelPool.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/pool/NoopChannelPool.java deleted file mode 100644 index 30a2d823e1..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/channel/pool/NoopChannelPool.java +++ /dev/null @@ -1,52 +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.netty.channel.pool; - -import org.asynchttpclient.netty.channel.pool.ChannelPoolPartitionSelector; -import org.jboss.netty.channel.Channel; - -public class NoopChannelPool implements ChannelPool { - - @Override - public boolean offer(Channel connection, Object partitionKey) { - return false; - } - - @Override - public Channel poll(Object partitionKey) { - return null; - } - - @Override - public boolean removeAll(Channel connection) { - return false; - } - - @Override - public boolean isOpen() { - return true; - } - - @Override - public void destroy() { - } - - @Override - public void flushPartition(Object partitionKey) { - } - - @Override - public void flushPartitions(ChannelPoolPartitionSelector selector) { - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/DefaultConnectionStrategy.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/DefaultConnectionStrategy.java deleted file mode 100644 index c9d8a6ca17..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/DefaultConnectionStrategy.java +++ /dev/null @@ -1,60 +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.netty.handler; - -import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONNECTION; -import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.CLOSE; -import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.KEEP_ALIVE; - -import org.asynchttpclient.channel.pool.ConnectionStrategy; -import org.jboss.netty.handler.codec.http.HttpMessage; -import org.jboss.netty.handler.codec.http.HttpRequest; -import org.jboss.netty.handler.codec.http.HttpResponse; -import org.jboss.netty.handler.codec.http.HttpVersion; - -/** - * Connection strategy implementing standard HTTP 1.0/1.1 behaviour. - */ -public class DefaultConnectionStrategy implements ConnectionStrategy { - - /** - * Implemented in accordance with RFC 7230 section 6.1 - * https://tools.ietf.org/html/rfc7230#section-6.1 - */ - @Override - public boolean keepAlive(HttpRequest request, HttpResponse response) { - - String responseConnectionHeader = connectionHeader(response); - - - if (CLOSE.equalsIgnoreCase(responseConnectionHeader)) { - return false; - } else { - String requestConnectionHeader = connectionHeader(request); - - if (request.getProtocolVersion() == HttpVersion.HTTP_1_0) { - // only use keep-alive if both parties agreed upon it - return KEEP_ALIVE.equalsIgnoreCase(requestConnectionHeader) && KEEP_ALIVE.equalsIgnoreCase(responseConnectionHeader); - - } else { - // 1.1+, keep-alive is default behavior - return !CLOSE.equalsIgnoreCase(requestConnectionHeader); - } - } - } - - private String connectionHeader(HttpMessage message) { - return message.headers().get(CONNECTION); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/HttpProtocol.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/HttpProtocol.java deleted file mode 100644 index 0a561c8754..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/HttpProtocol.java +++ /dev/null @@ -1,507 +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.netty.handler; - -import static org.asynchttpclient.ntlm.NtlmUtils.getNTLM; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getExplicitPort; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; -import static org.jboss.netty.handler.codec.http.HttpResponseStatus.CONTINUE; -import static org.jboss.netty.handler.codec.http.HttpResponseStatus.OK; -import static org.jboss.netty.handler.codec.http.HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED; -import static org.jboss.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.List; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHandler.State; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Realm.AuthScheme; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.channel.pool.ConnectionStrategy; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.netty.NettyResponseBodyPart; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.NettyResponseHeaders; -import org.asynchttpclient.netty.NettyResponseStatus; -import org.asynchttpclient.netty.channel.ChannelManager; -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.asynchttpclient.uri.Uri; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.handler.codec.http.HttpChunk; -import org.jboss.netty.handler.codec.http.HttpChunkTrailer; -import org.jboss.netty.handler.codec.http.HttpHeaders; -import org.jboss.netty.handler.codec.http.HttpMethod; -import org.jboss.netty.handler.codec.http.HttpRequest; -import org.jboss.netty.handler.codec.http.HttpResponse; - -public final class HttpProtocol extends Protocol { - - private final ConnectionStrategy connectionStrategy; - - public HttpProtocol(ChannelManager channelManager, AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, - NettyRequestSender requestSender) { - super(channelManager, config, nettyConfig, requestSender); - - connectionStrategy = nettyConfig.getConnectionStrategy(); - } - - private Realm kerberosChallenge(Channel channel,// - List authHeaders,// - Request request,// - FluentCaseInsensitiveStringsMap headers,// - Realm realm,// - NettyResponseFuture future) { - - Uri uri = request.getUri(); - String host = request.getVirtualHost() == null ? uri.getHost() : request.getVirtualHost(); - try { - String challengeHeader = SpnegoEngine.instance().generateToken(host); - headers.remove(HttpHeaders.Names.AUTHORIZATION); - headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); - - return new Realm.RealmBuilder().clone(realm)// - .setUri(uri)// - .setMethodName(request.getMethod())// - .setScheme(Realm.AuthScheme.KERBEROS)// - .build(); - - - } catch (SpnegoEngineException throwable) { - String ntlmAuthenticate = getNTLM(authHeaders); - if (ntlmAuthenticate != null) { - return ntlmChallenge(ntlmAuthenticate, request, headers, realm, future); - } - requestSender.abort(channel, future, throwable); - return null; - } - } - - private Realm kerberosProxyChallenge(Channel channel,// - List proxyAuth,// - Request request,// - ProxyServer proxyServer,// - FluentCaseInsensitiveStringsMap headers,// - NettyResponseFuture future) { - - try { - String challengeHeader = SpnegoEngine.instance().generateToken(proxyServer.getHost()); - headers.remove(HttpHeaders.Names.AUTHORIZATION); - headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); - - return proxyServer.realmBuilder()// - .setUri(request.getUri())// - .setMethodName(request.getMethod())// - .setScheme(Realm.AuthScheme.KERBEROS)// - .build(); - - } catch (SpnegoEngineException throwable) { - String ntlmAuthenticate = getNTLM(proxyAuth); - if (ntlmAuthenticate != null) { - return ntlmProxyChallenge(ntlmAuthenticate, request, proxyServer, headers, future); - } - 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(String authenticateHeader,// - Request request,// - FluentCaseInsensitiveStringsMap headers,// - Realm realm,// - NettyResponseFuture future) { - - if (authenticateHeader.equals("NTLM")) { - // server replied bare NTLM => we didn't preemptively sent Type1Msg - String challengeHeader = NtlmEngine.INSTANCE.generateType1Msg(); - - addNTLMAuthorizationHeader(headers, challengeHeader, false); - future.getAndSetAuth(false); - - } else { - // probably receiving Type2Msg, so we issue Type3Msg - addType3NTLMAuthorizationHeader(authenticateHeader, headers, realm, false); - } - - return new Realm.RealmBuilder().clone(realm)// - .setUri(request.getUri())// - .setMethodName(request.getMethod())// - .build(); - } - - private Realm ntlmProxyChallenge(String authenticateHeader,// - Request request,// - ProxyServer proxyServer,// - FluentCaseInsensitiveStringsMap headers,// - NettyResponseFuture future) { - - future.getAndSetAuth(false); - headers.remove(HttpHeaders.Names.PROXY_AUTHORIZATION); - - Realm realm = proxyServer.realmBuilder()// - .setScheme(AuthScheme.NTLM)// - .setUri(request.getUri())// - .setMethodName(request.getMethod()).build(); - - addType3NTLMAuthorizationHeader(authenticateHeader, headers, realm, true); - - return realm; - } - - private void addType3NTLMAuthorizationHeader(String auth, FluentCaseInsensitiveStringsMap headers, Realm realm, boolean proxyInd) { - headers.remove(authorizationHeaderName(proxyInd)); - - if (isNonEmpty(auth) && auth.startsWith("NTLM ")) { - String serverChallenge = auth.substring("NTLM ".length()).trim(); - String challengeHeader = NtlmEngine.INSTANCE.generateType3Msg(realm.getPrincipal(), realm.getPassword(), realm.getNtlmDomain(), realm.getNtlmHost(), serverChallenge); - addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd); - } - } - - private void finishUpdate(final NettyResponseFuture future, Channel channel, boolean expectOtherChunks) { - - future.cancelTimeouts(); - - boolean keepAlive = future.isKeepAlive(); - if (expectOtherChunks && keepAlive) - channelManager.drainChannelAndOffer(channel, future); - else - channelManager.tryToOfferChannelToPool(channel, future.getAsyncHandler(), keepAlive, future.getPartitionKey()); - - try { - future.done(); - } catch (Exception t) { - // Never propagate exception once we know we are done. - logger.debug(t.getMessage(), t); - } - } - - 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 boolean exitAfterHandling401(// - final Channel channel,// - final NettyResponseFuture future,// - HttpResponse response,// - final Request request,// - int statusCode,// - Realm realm,// - ProxyServer proxyServer) { - - if (statusCode == UNAUTHORIZED.getCode() && 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"); - String ntlmAuthenticate = getNTLM(wwwAuthHeaders); - if (!wwwAuthHeaders.contains("Kerberos") && ntlmAuthenticate != null) { - // NTLM - newRealm = ntlmChallenge(ntlmAuthenticate, request, request.getHeaders(), realm, future); - - } else if (negociate) { - // SPNEGO KERBEROS - newRealm = kerberosChallenge(channel, wwwAuthHeaders, request, request.getHeaders(), realm, future); - if (newRealm == null) - return true; - - } else { - newRealm = new Realm.RealmBuilder()// - .clone(realm)// - .setUri(request.getUri())// - .setMethodName(request.getMethod())// - .setUsePreemptiveAuth(true)// - .parseWWWAuthenticateHeader(wwwAuthHeaders.get(0))// - .build(); - } - - final Request nextRequest = new RequestBuilder(future.getRequest()).setHeaders(request.getHeaders()).setRealm(newRealm).build(); - - logger.debug("Sending authentication to {}", request.getUri()); - if (future.isKeepAlive() && !HttpHeaders.isTransferEncodingChunked(response) && !response.isChunked()) { - future.setReuseChannel(true); - } else { - channelManager.closeChannel(channel); - } - - requestSender.sendNextRequest(nextRequest, future); - return true; - } - } - - return false; - } - - private boolean exitAfterHandling100(final Channel channel, final NettyResponseFuture future, int statusCode) { - if (statusCode == CONTINUE.getCode()) { - future.setHeadersAlreadyWrittenOnContinue(true); - future.setDontWriteBodyBecauseExpectContinue(false); - requestSender.writeRequest(future, channel); - return true; - - } - return false; - } - - private boolean exitAfterHandling407(// - final Channel channel,// - final NettyResponseFuture future,// - HttpResponse response,// - Request request,// - int statusCode,// - Realm realm,// - ProxyServer proxyServer) { - - if (statusCode == PROXY_AUTHENTICATION_REQUIRED.getCode() && 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"); - String ntlmAuthenticate = getNTLM(proxyAuthHeaders); - if (!proxyAuthHeaders.contains("Kerberos") && ntlmAuthenticate != null) { - newRealm = ntlmProxyChallenge(ntlmAuthenticate, request, proxyServer, requestHeaders, future); - // SPNEGO KERBEROS - - } else if (negociate) { - newRealm = kerberosProxyChallenge(channel, proxyAuthHeaders, request, proxyServer, requestHeaders, future); - if (newRealm == null) - return true; - - } else { - newRealm = new Realm.RealmBuilder().clone(realm)// - .setUri(request.getUri())// - .setOmitQuery(true)// - .setMethodName(request.getMethod())// - .setUsePreemptiveAuth(true)// - .parseProxyAuthenticateHeader(proxyAuthHeaders.get(0))// - .build(); - } - - final Request nextRequest = new RequestBuilder(future.getRequest()).setHeaders(request.getHeaders()).setRealm(newRealm).build(); - - logger.debug("Sending proxy authentication to {}", request.getUri()); - if (future.isKeepAlive() && !HttpHeaders.isTransferEncodingChunked(response) && !response.isChunked()) { - future.setConnectAllowed(true); - future.setReuseChannel(true); - } else { - channelManager.closeChannel(channel); - } - - 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) { - - if (statusCode == OK.getCode() && httpRequest.getMethod() == HttpMethod.CONNECT) { - - if (future.isKeepAlive()) - future.attachChannel(channel, true); - - Uri requestUri = request.getUri(); - String scheme = requestUri.getScheme(); - String host = requestUri.getHost(); - int port = getExplicitPort(requestUri); - - logger.debug("Connecting to proxy {} for scheme {}", proxyServer, scheme); - - try { - channelManager.upgradeProtocol(channel.getPipeline(), scheme, host, port); - future.setReuseChannel(true); - future.setConnectAllowed(false); - requestSender.sendNextRequest(new RequestBuilder(future.getRequest()).build(), future); - - } catch (GeneralSecurityException ex) { - requestSender.abort(channel, future, ex); - } - - return true; - } - - return false; - } - - private boolean exitAfterHandlingStatus(Channel channel, NettyResponseFuture future, HttpResponse response, - AsyncHandler handler, NettyResponseStatus status) throws 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 Exception { - if (!response.headers().isEmpty() && handler.onHeadersReceived(responseHeaders) != State.CONTINUE) { - finishUpdate(future, channel, HttpHeaders.isTransferEncodingChunked(response)); - return true; - } - return false; - } - - // Netty 3: if the response is not chunked, the full body comes with the response - private boolean exitAfterHandlingBody(Channel channel, NettyResponseFuture future, HttpResponse response, - AsyncHandler handler) throws Exception { - if (!response.isChunked()) { - // no chunks expected, exiting - if (response.getContent().readableBytes() > 0) { - // no need to notify an empty bodypart - updateBodyAndInterrupt(future, handler, new NettyResponseBodyPart(response, null, true)); - } - finishUpdate(future, channel, false); - 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(connectionStrategy.keepAlive(httpRequest, response)); - - NettyResponseStatus status = new NettyResponseStatus(future.getUri(), config, response, channel); - int statusCode = response.getStatus().getCode(); - 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, realm) || // - exitAfterHandlingConnect(channel, future, request, proxyServer, statusCode, httpRequest) || // - exitAfterHandlingStatus(channel, future, response, handler, status) || // - exitAfterHandlingHeaders(channel, future, response, handler, responseHeaders) || // - exitAfterHandlingBody(channel, future, response, handler); - } - - private void handleChunk(HttpChunk chunk,// - final Channel channel,// - final NettyResponseFuture future,// - AsyncHandler handler) throws Exception { - - boolean last = chunk.isLast(); - // we don't notify updateBodyAndInterrupt with the last chunk as it's empty - if (last || updateBodyAndInterrupt(future, handler, new NettyResponseBodyPart(null, chunk, last))) { - - // only possible if last is true - if (chunk instanceof HttpChunkTrailer) { - HttpChunkTrailer chunkTrailer = (HttpChunkTrailer) chunk; - if (!chunkTrailer.trailingHeaders().isEmpty()) { - NettyResponseHeaders responseHeaders = new NettyResponseHeaders(future.getHttpHeaders(), chunkTrailer.trailingHeaders()); - handler.onHeadersReceived(responseHeaders); - } - } - finishUpdate(future, channel, !chunk.isLast()); - } - } - - @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) { - if (handleHttpResponse((HttpResponse) e, channel, future, handler)) - return; - - } else if (e instanceof HttpChunk) - handleChunk((HttpChunk) 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.class.cast(t), channel)) { - return; - } - - // FIXME Weird: close channel in abort, then close again - try { - requestSender.abort(channel, future, t); - } catch (Exception abortException) { - logger.debug("Abort failed", abortException); - } finally { - finishUpdate(future, channel, false); - } - throw t; - } - } - - public void onError(NettyResponseFuture future, Throwable e) { - } - - public void onClose(NettyResponseFuture future) { - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/Processor.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/Processor.java deleted file mode 100644 index e9330a4c4c..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/Processor.java +++ /dev/null @@ -1,184 +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.netty.handler; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.CHANNEL_CLOSED_EXCEPTION; - -import java.io.IOException; -import java.nio.channels.ClosedChannelException; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.Callback; -import org.asynchttpclient.netty.DiscardEvent; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.ChannelManager; -import org.asynchttpclient.netty.channel.Channels; -import org.asynchttpclient.netty.future.StackTraceInspector; -import org.asynchttpclient.netty.request.NettyRequestSender; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.ExceptionEvent; -import org.jboss.netty.channel.MessageEvent; -import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.handler.codec.PrematureChannelClosureException; -import org.jboss.netty.handler.codec.http.HttpChunk; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class Processor extends SimpleChannelUpstreamHandler { - - private static final Logger LOGGER = LoggerFactory.getLogger(Processor.class); - - 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 messageReceived(final ChannelHandlerContext ctx, MessageEvent e) throws Exception { - - // call super to reset the read timeout - super.messageReceived(ctx, e); - - Channel channel = ctx.getChannel(); - Object attribute = Channels.getAttribute(channel); - - if (attribute instanceof Callback) { - Object message = e.getMessage(); - Callback ac = (Callback) attribute; - if (message instanceof HttpChunk) { - // the AsyncCallable is to be processed on the last chunk - if (HttpChunk.class.cast(message).isLast()) - // process the AsyncCallable before passing the message to the protocol - ac.call(); - // FIXME remove attribute? - } else { - LOGGER.info("Received unexpected message while expecting a chunk: " + message); - ac.call(); - Channels.setDiscard(channel); - } - - } else if (attribute instanceof NettyResponseFuture) { - NettyResponseFuture future = (NettyResponseFuture) attribute; - protocol.handle(channel, future, e.getMessage()); - - } else if (attribute != DiscardEvent.INSTANCE) { - // unhandled message - LOGGER.debug("Orphan channel {} with attribute {} received message {}, closing", channel, attribute, e.getMessage()); - Channels.silentlyCloseChannel(channel); - } - } - - @Override - public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { - - if (requestSender.isClosed()) - return; - - Channel channel = ctx.getChannel(); - channelManager.removeAll(channel); - - try { - super.channelClosed(ctx, e); - } 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) attribute; - future.touch(); - - if (!config.getIOExceptionFilters().isEmpty() - && requestSender.applyIoExceptionFiltersAndReplayRequest(future, CHANNEL_CLOSED_EXCEPTION, channel)) - return; - - protocol.onClose(future); - requestSender.handleUnexpectedClosedChannel(channel, future); - } - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { - Channel channel = ctx.getChannel(); - Throwable cause = e.getCause(); - NettyResponseFuture future = null; - - // FIXME we can't get a PrematureChannelClosureException as we create the HttpClientCodec without setting failOnMissingResponse to true - if (cause instanceof PrematureChannelClosureException || cause instanceof ClosedChannelException) - return; - - 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. - Channels.silentlyCloseChannel(channel); - return; - } - } - - // FIXME how does recovery occur?! - if (StackTraceInspector.recoverOnReadOrWriteException(cause)) { - LOGGER.debug("Trying to recover from dead Channel: {}", channel); - return; - } - } else if (attribute instanceof Callback) { - future = ((Callback) 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.getCause()); - } catch (Throwable t) { - LOGGER.error(t.getMessage(), t); - } - - channelManager.closeChannel(channel); - ctx.sendUpstream(e); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/Protocol.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/Protocol.java deleted file mode 100644 index ae0a04574d..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/Protocol.java +++ /dev/null @@ -1,225 +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.netty.handler; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.followRedirect; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.isSameBase; -import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.AUTHORIZATION; -import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.PROXY_AUTHORIZATION; -import static org.jboss.netty.handler.codec.http.HttpResponseStatus.FOUND; -import static org.jboss.netty.handler.codec.http.HttpResponseStatus.MOVED_PERMANENTLY; -import static org.jboss.netty.handler.codec.http.HttpResponseStatus.SEE_OTHER; -import static org.jboss.netty.handler.codec.http.HttpResponseStatus.TEMPORARY_REDIRECT; - -import java.util.HashSet; -import java.util.Set; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Realm.AuthScheme; -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.FilterException; -import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.handler.MaxRedirectException; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.ChannelManager; -import org.asynchttpclient.netty.request.NettyRequestSender; -import org.asynchttpclient.uri.Uri; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.handler.codec.http.HttpHeaders; -import org.jboss.netty.handler.codec.http.HttpResponse; -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 MaxRedirectException maxRedirectException; - - public static final Set REDIRECT_STATUSES = new HashSet<>(); - static { - REDIRECT_STATUSES.add(MOVED_PERMANENTLY.getCode()); - REDIRECT_STATUSES.add(FOUND.getCode()); - REDIRECT_STATUSES.add(SEE_OTHER.getCode()); - REDIRECT_STATUSES.add(TEMPORARY_REDIRECT.getCode()); - } - - public Protocol(ChannelManager channelManager, AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, NettyRequestSender requestSender) { - this.channelManager = channelManager; - this.config = config; - this.nettyConfig = nettyConfig; - this.requestSender = requestSender; - - hasResponseFilters = !config.getResponseFilters().isEmpty(); - hasIOExceptionFilters = !config.getIOExceptionFilters().isEmpty(); - maxRedirectException = new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()); - } - - public abstract void handle(Channel channel, NettyResponseFuture future, Object message) throws Exception; - - public abstract void onError(NettyResponseFuture future, Throwable e); - - public abstract void onClose(NettyResponseFuture future); - - private FluentCaseInsensitiveStringsMap propagatedHeaders(Request request, Realm realm, boolean switchToGet) { - - FluentCaseInsensitiveStringsMap headers = request.getHeaders()// - .delete(HttpHeaders.Names.HOST)// - .delete(HttpHeaders.Names.CONTENT_LENGTH)// - .delete(HttpHeaders.Names.CONTENT_TYPE); - - if (realm != null && realm.getScheme() == AuthScheme.NTLM) { - headers.delete(AUTHORIZATION)// - .delete(PROXY_AUTHORIZATION); - } - return headers; - } - - protected boolean exitAfterHandlingRedirect(// - Channel channel,// - NettyResponseFuture future,// - HttpResponse response,// - Request request,// - int statusCode,// - Realm realm) throws Exception { - - if (followRedirect(config, request) && REDIRECT_STATUSES.contains(statusCode)) { - if (future.incrementAndGetCurrentRedirectCount() >= config.getMaxRedirects()) { - throw maxRedirectException; - - } else { - // We must allow 401 handling again. - future.getAndSetAuth(false); - - // if we are to strictly handle 302, we should keep the - // original method (which browsers don't) - // 303 must force GET - String originalMethod = request.getMethod(); - boolean switchToGet = !originalMethod.equals("GET") && (statusCode == 303 || (statusCode == 302 && !config.isStrict302Handling())); - - final RequestBuilder requestBuilder = new RequestBuilder(switchToGet ? "GET" : originalMethod)// - .setCookies(request.getCookies())// - .setConnectionPoolPartitioning(request.getConnectionPoolPartitioning())// - .setFollowRedirect(true)// - .setLocalInetAddress(request.getLocalAddress())// - .setNameResolver(request.getNameResolver())// - .setProxyServer(request.getProxyServer())// - .setRealm(request.getRealm())// - .setRequestTimeout(request.getRequestTimeout()); - - requestBuilder.setHeaders(propagatedHeaders(request, realm, switchToGet)); - - // 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(HttpHeaders.Names.LOCATION); - Uri newUri = Uri.create(future.getUri(), location); - - logger.debug("Redirecting to {}", newUri); - - for (String cookieStr : responseHeaders.getAll(HttpHeaders.Names.SET_COOKIE)) { - Cookie c = CookieDecoder.decode(cookieStr); - if (c != null) - requestBuilder.addOrReplaceCookie(c); - } - - requestBuilder.setHeaders(propagatedHeaders(future.getRequest(), realm, switchToGet)); - - boolean sameBase = isSameBase(request.getUri(), 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(); - - logger.debug("Sending redirect to {}", newUri); - - if (future.isKeepAlive() && !HttpHeaders.isTransferEncodingChunked(response) && !response.isChunked()) { - - if (sameBase) { - future.setReuseChannel(true); - } else { - channelManager.drainChannelAndOffer(channel, future, initialConnectionKeepAlive, initialPartitionKey); - } - - } else { - // redirect + chunking = WAT - channelManager.closeChannel(channel); - } - - requestSender.sendNextRequest(nextRequest, future); - return true; - } - } - return false; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - protected boolean exitAfterProcessingFilters(// - Channel channel,// - NettyResponseFuture future,// - AsyncHandler handler, // - HttpResponseStatus status,// - HttpResponseHeaders responseHeaders) { - - 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/netty3/src/main/java/org/asynchttpclient/netty/handler/WebSocketProtocol.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/WebSocketProtocol.java deleted file mode 100644 index dca287d4e4..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/handler/WebSocketProtocol.java +++ /dev/null @@ -1,209 +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.netty.handler; - -import static org.asynchttpclient.ws.WebSocketUtils.getAcceptKey; -import static org.jboss.netty.handler.codec.http.HttpResponseStatus.SWITCHING_PROTOCOLS; - -import java.io.IOException; -import java.util.Locale; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.AsyncHandler.State; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.netty.NettyResponseBodyPart; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.NettyResponseHeaders; -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 org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.handler.codec.http.HttpChunk; -import org.jboss.netty.handler.codec.http.HttpHeaders; -import org.jboss.netty.handler.codec.http.HttpResponse; -import org.jboss.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame; - -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, config)); - } 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; - HttpResponseStatus status = new NettyResponseStatus(future.getUri(), config, response, channel); - HttpResponseHeaders responseHeaders = new NettyResponseHeaders(response.headers()); - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - - if (exitAfterProcessingFilters(channel, future, handler, status, responseHeaders)) { - return; - } - - future.setHttpHeaders(response.headers()); - if (exitAfterHandlingRedirect(channel, future, response, request, response.getStatus().getCode(), realm)) - 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); - 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.getPipeline()); - - 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.getStatusCode(), closeFrame.getReasonText()); - - } else if (frame.getBinaryData() != null) { - HttpChunk webSocketChunk = new HttpChunk() { - private ChannelBuffer content = frame.getBinaryData(); - - @Override - public boolean isLast() { - return frame.isFinalFragment(); - } - - @Override - public ChannelBuffer getContent() { - return content; - } - - @Override - public void setContent(ChannelBuffer content) { - throw new UnsupportedOperationException(); - } - }; - - NettyResponseBodyPart part = new NettyResponseBodyPart(null, webSocketChunk, 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); - } - } - } 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) future.getAsyncHandler(); - - 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) future.getAsyncHandler(); - 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/netty3/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java deleted file mode 100644 index ddb0ffcb22..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java +++ /dev/null @@ -1,36 +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.netty.request; - -import org.asynchttpclient.netty.request.body.NettyBody; -import org.jboss.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/netty3/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java deleted file mode 100644 index 7a25bccc53..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java +++ /dev/null @@ -1,216 +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.netty.request; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.DEFAULT_CHARSET; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.hostHeader; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.urlEncodeFormParams; -import static org.asynchttpclient.util.HttpUtils.isSecure; -import static org.asynchttpclient.util.HttpUtils.isWebSocket; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; -import static org.asynchttpclient.ws.WebSocketUtils.getKey; -import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*; - -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map.Entry; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.cookie.CookieEncoder; -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.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.HttpUtils; -import org.asynchttpclient.util.StringUtils; -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.handler.codec.http.DefaultHttpRequest; -import org.jboss.netty.handler.codec.http.HttpHeaders; -import org.jboss.netty.handler.codec.http.HttpMethod; -import org.jboss.netty.handler.codec.http.HttpRequest; -import org.jboss.netty.handler.codec.http.HttpVersion; - -public final class NettyRequestFactory extends NettyRequestFactoryBase { - - public static final String GZIP_DEFLATE = HttpHeaders.Values.GZIP + "," + HttpHeaders.Values.DEFLATE; - - public NettyRequestFactory(AsyncHttpClientConfig config) { - super(config); - } - - protected List getProxyAuthorizationHeader(Request request) { - return request.getHeaders().get(PROXY_AUTHORIZATION); - } - - private NettyBody body(Request request, boolean connect) { - NettyBody nettyBody = null; - if (!connect) { - - Charset bodyCharset = request.getBodyCharset() == null ? DEFAULT_CHARSET : request.getBodyCharset(); - - 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.getStreamData() != null) - nettyBody = new NettyInputStreamBody(request.getStreamData(), config); - - else if (isNonEmpty(request.getFormParams())) { - - String contentType = null; - if (!request.getHeaders().containsKey(CONTENT_TYPE)) - contentType = HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED; - - nettyBody = new NettyByteBufferBody(urlEncodeFormParams(request.getFormParams(), bodyCharset), contentType); - - } else if (isNonEmpty(request.getParts())) - nettyBody = new NettyMultipartBody(request.getParts(), 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) - nettyBody = new NettyInputStreamBody(InputStreamBodyGenerator.class.cast(request.getBodyGenerator()).getInputStream(), config); - - 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 forceConnect, ProxyServer proxyServer) { - - Uri uri = request.getUri(); - HttpMethod method = forceConnect ? HttpMethod.CONNECT : HttpMethod.valueOf(request.getMethod()); - boolean connect = method == HttpMethod.CONNECT; - - boolean allowConnectionPooling = config.isAllowPoolingConnections() && (!HttpUtils.isSecure(uri) || config.isAllowPoolingSslConnections()); - - HttpVersion httpVersion = !allowConnectionPooling || (connect && proxyServer.isForceHttp10()) ? HttpVersion.HTTP_1_0 : HttpVersion.HTTP_1_1; - String requestUri = requestUri(uri, proxyServer, connect); - - NettyBody body = body(request, connect); - - HttpRequest httpRequest; - NettyRequest nettyRequest; - if (body instanceof NettyDirectBody) { - ChannelBuffer buffer = NettyDirectBody.class.cast(body).channelBuffer(); - httpRequest = new DefaultHttpRequest(httpVersion, method, requestUri); - // body is passed as null as it's written directly with the request - httpRequest.setContent(buffer); - nettyRequest = new NettyRequest(httpRequest, null); - - } else { - httpRequest = new DefaultHttpRequest(httpVersion, method, requestUri); - nettyRequest = new NettyRequest(httpRequest, body); - } - - HttpHeaders headers = httpRequest.headers(); - - if (!connect) { - // assign headers as configured on request - for (Entry> header : request.getHeaders()) { - headers.set(header.getKey(), header.getValue()); - } - - if (isNonEmpty(request.getCookies())) - headers.set(COOKIE, CookieEncoder.encode(request.getCookies())); - - if (config.isCompressionEnforced() && !headers.contains(ACCEPT_ENCODING)) - headers.set(ACCEPT_ENCODING, GZIP_DEFLATE); - } - - if (body != null) { - if (body.getContentLength() < 0) - headers.set(TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); - else - headers.set(CONTENT_LENGTH, body.getContentLength()); - - if (body.getContentType() != null) - headers.set(CONTENT_TYPE, body.getContentType()); - } - - // connection header and friends - boolean webSocket = isWebSocket(uri.getScheme()); - if (!connect && webSocket) { - String origin = "http://" + uri.getHost() + ":" + (uri.getPort() == -1 ? isSecure(uri.getScheme()) ? 443 : 80 : uri.getPort()); - headers.set(UPGRADE, HttpHeaders.Values.WEBSOCKET)// - .set(CONNECTION, HttpHeaders.Values.UPGRADE)// - .set(ORIGIN, origin)// - .set(SEC_WEBSOCKET_KEY, getKey())// - .set(SEC_WEBSOCKET_VERSION, "13"); - - } else if (!headers.contains(CONNECTION)) { - String connectionHeaderValue = connectionHeader(allowConnectionPooling, httpVersion == HttpVersion.HTTP_1_1); - if (connectionHeaderValue != null) - headers.set(CONNECTION, connectionHeaderValue); - } - - if (!headers.contains(HOST)) - headers.set(HOST, hostHeader(request, uri)); - - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - - // don't override authorization but append - addAuthorizationHeader(headers, systematicAuthorizationHeader(request, realm)); - - setProxyAuthorizationHeader(headers, systematicProxyAuthorizationHeader(request, proxyServer, realm, connect)); - - // Add default accept headers - if (!headers.contains(ACCEPT)) - headers.set(ACCEPT, "*/*"); - - // Add default user agent - if (!headers.contains(USER_AGENT) && config.getUserAgent() != null) - headers.set(USER_AGENT, config.getUserAgent()); - - return nettyRequest; - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java deleted file mode 100644 index 7e72af6498..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java +++ /dev/null @@ -1,512 +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.netty.request; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.REMOTELY_CLOSED_EXCEPTION; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getExplicitPort; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.requestTimeout; -import static org.asynchttpclient.util.HttpUtils.WS; -import static org.asynchttpclient.util.HttpUtils.useProxyConnect; -import static org.asynchttpclient.util.ProxyUtils.avoidProxy; -import static org.asynchttpclient.util.ProxyUtils.getProxyServer; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.IOExceptionFilter; -import org.asynchttpclient.handler.AsyncHandlerExtensions; -import org.asynchttpclient.handler.TransferCompletionHandler; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.ChannelManager; -import org.asynchttpclient.netty.channel.Channels; -import org.asynchttpclient.netty.channel.NettyConnectListener; -import org.asynchttpclient.netty.timeout.ReadTimeoutTimerTask; -import org.asynchttpclient.netty.timeout.RequestTimeoutTimerTask; -import org.asynchttpclient.netty.timeout.TimeoutsHolder; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.uri.Uri; -import org.asynchttpclient.ws.WebSocketUpgradeHandler; -import org.jboss.netty.bootstrap.ClientBootstrap; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.handler.codec.http.HttpHeaders; -import org.jboss.netty.handler.codec.http.HttpMethod; -import org.jboss.netty.handler.codec.http.HttpRequest; -import org.jboss.netty.util.Timeout; -import org.jboss.netty.util.Timer; -import org.jboss.netty.util.TimerTask; -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,// - ChannelManager channelManager,// - Timer nettyTimer,// - AtomicBoolean closed) { - this.config = config; - this.channelManager = channelManager; - this.nettyTimer = nettyTimer; - this.closed = closed; - requestFactory = new NettyRequestFactory(config); - } - - public ListenableFuture sendRequest(final Request request,// - final AsyncHandler asyncHandler,// - NettyResponseFuture future,// - boolean reclaimCache) { - - if (closed.get()) - throw new IllegalStateException("Closed"); - - validateWebSocketRequest(request, asyncHandler); - - 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(request.getUri())) - // SSL proxy, have to handle CONNECT - if (future != null && future.isConnectAllowed()) - // CONNECT forced - return sendRequestWithCertainForceConnect(request, asyncHandler, future, reclaimCache, proxyServer, true, true); - else - return sendRequestThroughSslProxy(request, asyncHandler, future, reclaimCache, proxyServer); - else - return sendRequestWithCertainForceConnect(request, asyncHandler, future, reclaimCache, 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,// - ProxyServer proxyServer,// - boolean useProxy,// - boolean forceConnect) { - NettyResponseFuture newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, proxyServer, forceConnect); - - Channel channel = getCachedChannel(future, request, proxyServer, asyncHandler); - - if (Channels.isChannelValid(channel)) - return sendRequestWithCachedChannel(request, proxyServer, newFuture, asyncHandler, channel); - else - return sendRequestWithNewChannel(request, 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,// - ProxyServer proxyServer) { - - NettyResponseFuture newFuture = null; - for (int i = 0; i < 3; i++) { - Channel channel = getCachedChannel(future, request, proxyServer, asyncHandler); - if (Channels.isChannelValid(channel)) - if (newFuture == null) - newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, proxyServer, false); - - if (Channels.isChannelValid(channel)) - // if the channel is still active, we can use it, otherwise try - // gain - return sendRequestWithCachedChannel(request, proxyServer, newFuture, asyncHandler, channel); - else - // pool is empty - break; - } - - newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, proxyServer, true); - return sendRequestWithNewChannel(request, proxyServer, true, newFuture, asyncHandler, reclaimCache); - } - - private NettyResponseFuture newNettyRequestAndResponseFuture(final Request request, final AsyncHandler asyncHandler, NettyResponseFuture originalFuture, - ProxyServer proxy, boolean forceConnect) { - - NettyRequest nettyRequest = requestFactory.newNettyRequest(request, forceConnect, proxy); - - if (originalFuture == null) { - return newNettyResponseFuture(request, asyncHandler, nettyRequest, proxy); - } else { - originalFuture.setNettyRequest(nettyRequest); - originalFuture.setRequest(request); - return originalFuture; - } - } - - private Channel getCachedChannel(NettyResponseFuture future, Request request, ProxyServer proxyServer, AsyncHandler asyncHandler) { - - if (future != null && future.reuseChannel() && Channels.isChannelValid(future.channel())) - return future.channel(); - else - return pollAndVerifyCachedChannel(request, proxyServer, asyncHandler); - } - - private ListenableFuture sendRequestWithCachedChannel(Request request, ProxyServer proxy, NettyResponseFuture future, AsyncHandler asyncHandler, Channel channel) { - - if (asyncHandler instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(asyncHandler).onConnectionPooled(channel); - - future.setState(NettyResponseFuture.STATE.POOLED); - future.attachChannel(channel, false); - - LOGGER.debug("Using cached Channel {} for {} '{}'", channel, future.getNettyRequest().getHttpRequest().getMethod(), future.getNettyRequest().getHttpRequest().getUri()); - - if (Channels.isChannelValid(channel)) { - Channels.setAttribute(channel, future); - - 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,// - boolean useProxy,// - NettyResponseFuture future,// - AsyncHandler asyncHandler,// - boolean reclaimCache) { - - // some headers are only set when performing the first request - HttpHeaders headers = future.getNettyRequest().getHttpRequest().headers(); - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - boolean connect = future.getNettyRequest().getHttpRequest().getMethod() == HttpMethod.CONNECT; - requestFactory.addAuthorizationHeader(headers, requestFactory.firstRequestOnlyAuthorizationHeader(request, proxy, realm)); - requestFactory.setProxyAuthorizationHeader(headers, requestFactory.firstRequestOnlyProxyAuthorizationHeader(request, proxy, connect)); - - // 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? - ClientBootstrap bootstrap = channelManager.getBootstrap(request.getUri().getScheme(), useProxy); - - boolean channelPreempted = false; - Object partitionKey = future.getPartitionKey(); - - try { - // Do not throw an exception when we need an extra connection for a - // redirect. - if (!reclaimCache) { - channelManager.preemptChannel(partitionKey); - channelPreempted = true; - } - - if (asyncHandler instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(asyncHandler).onConnectionOpen(); - - ChannelFuture channelFuture = connect(request, proxy, useProxy, bootstrap, asyncHandler); - channelFuture.addListener(new NettyConnectListener(future, this, channelManager, channelPreempted, partitionKey)); - - } catch (Throwable t) { - if (channelPreempted) - channelManager.abortChannelPreemption(partitionKey); - - abort(null, future, t.getCause() == null ? t : t.getCause()); - } - - return future; - } - - private NettyResponseFuture newNettyResponseFuture(Request request, AsyncHandler asyncHandler, NettyRequest nettyRequest, ProxyServer proxyServer) { - - NettyResponseFuture future = new NettyResponseFuture<>(// - request,// - asyncHandler,// - nettyRequest,// - config.getMaxRequestRetry(),// - request.getConnectionPoolPartitioning(),// - 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) { - - NettyRequest nettyRequest = future.getNettyRequest(); - HttpRequest httpRequest = nettyRequest.getHttpRequest(); - AsyncHandler handler = 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.isChannelValid(channel)) - return; - - try { - if (handler instanceof TransferCompletionHandler) - configureTransferAdapter(handler, httpRequest); - - if (!future.isHeadersAlreadyWrittenOnContinue()) { - if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onRequestSend(nettyRequest); - channel.write(httpRequest).addListener(new ProgressListener(config, future.getAsyncHandler(), future, true)); - } - - if (!future.isDontWriteBodyBecauseExpectContinue() && httpRequest.getMethod() != HttpMethod.CONNECT && nettyRequest.getBody() != null) - nettyRequest.getBody().write(channel, future); - - // don't bother scheduling timeouts if channel became invalid - if (Channels.isChannelValid(channel)) - scheduleTimeouts(future); - - } catch (Exception e) { - LOGGER.error("Can't write request", e); - abort(channel, future, e); - } - } - - private InetSocketAddress remoteAddress(Request request, ProxyServer proxy, boolean useProxy) throws UnknownHostException { - - InetAddress address; - Uri uri = request.getUri(); - int port = getExplicitPort(uri); - - if (request.getInetAddress() != null) { - address = request.getInetAddress(); - - } else if (!useProxy || avoidProxy(proxy, uri.getHost())) { - address = request.getNameResolver().resolve(uri.getHost()); - - } else { - address = request.getNameResolver().resolve(proxy.getHost()); - port = proxy.getPort(); - } - - return new InetSocketAddress(address, port); - } - - private ChannelFuture connect(Request request, ProxyServer proxy, boolean useProxy, ClientBootstrap bootstrap, AsyncHandler asyncHandler) throws UnknownHostException { - InetSocketAddress remoteAddress = remoteAddress(request, proxy, useProxy); - - if (asyncHandler instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(asyncHandler).onDnsResolved(remoteAddress.getAddress()); - - 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 transferCompletionHandler = (TransferCompletionHandler) handler; - transferCompletionHandler.patchForNetty3(); - transferCompletionHandler.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 readTimeoutValue = config.getReadTimeout(); - if (readTimeoutValue != -1 && readTimeoutValue < requestTimeoutInMs) { - // no need for a readTimeout that's less than the requestTimeout - Timeout readTimeout = newTimeout(new ReadTimeoutTimerTask(nettyResponseFuture, this, timeoutsHolder, requestTimeoutInMs, readTimeoutValue), readTimeoutValue); - timeoutsHolder.readTimeout = readTimeout; - } - 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()) { - future.setState(NettyResponseFuture.STATE.CLOSED); - LOGGER.debug("Aborting Future {}\n", future); - LOGGER.debug(t.getMessage(), t); - future.abort(t); - } - } - - public void handleUnexpectedClosedChannel(Channel channel, NettyResponseFuture future) { - if (future.isDone()) - channelManager.closeChannel(channel); - - else if (!retry(future)) - abort(channel, future, REMOTELY_CLOSED_EXCEPTION); - } - - public boolean retry(NettyResponseFuture future) { - - if (isClosed()) - return false; - - 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 (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().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(Request request, NettyResponseFuture future) { - sendRequest(request, future.getAsyncHandler(), future, true); - } - - private void validateWebSocketRequest(Request request, AsyncHandler asyncHandler) { - Uri uri = request.getUri(); - boolean isWs = uri.getScheme().startsWith(WS); - if (asyncHandler instanceof WebSocketUpgradeHandler) { - if (!isWs) - throw new IllegalArgumentException("WebSocketUpgradeHandler but scheme isn't ws or wss: " + uri.getScheme()); - else if (!request.getMethod().equals(HttpMethod.GET.getName())) - throw new IllegalArgumentException("WebSocketUpgradeHandler but method isn't GET: " + request.getMethod()); - } else if (isWs) { - throw new IllegalArgumentException("No WebSocketUpgradeHandler but scheme is " + uri.getScheme()); - } - } - - public Channel pollAndVerifyCachedChannel(Request request, ProxyServer proxy, AsyncHandler asyncHandler) { - - if (asyncHandler instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(asyncHandler).onConnectionPool(); - - Uri uri = request.getUri(); - String virtualHost = request.getVirtualHost(); - final Channel channel = channelManager.poll(uri, virtualHost, proxy, request.getConnectionPoolPartitioning()); - - if (channel != null) { - LOGGER.debug("Using cached Channel {}\n for uri {}\n", channel, uri); - - try { - // 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. - channelManager.verifyChannelPipeline(channel.getPipeline(), uri, virtualHost); - } catch (Exception ex) { - LOGGER.debug(ex.getMessage(), ex); - } - } - return channel; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void replayRequest(final NettyResponseFuture future, FilterContext fc, Channel channel) { - - final 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.drainChannelAndOffer(channel, future); - sendNextRequest(newRequest, future); - return; - } - - public boolean isClosed() { - return closed.get(); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/ProgressListener.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/ProgressListener.java deleted file mode 100644 index 21fc6f05a1..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/ProgressListener.java +++ /dev/null @@ -1,94 +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.netty.request; - -import java.nio.channels.ClosedChannelException; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Realm; -import org.asynchttpclient.handler.ProgressAsyncHandler; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.Channels; -import org.asynchttpclient.netty.future.StackTraceInspector; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.ChannelFutureProgressListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ProgressListener implements ChannelFutureProgressListener { - - private static final Logger LOGGER = LoggerFactory.getLogger(ProgressListener.class); - - private final AsyncHttpClientConfig config; - private final boolean notifyHeaders; - private final AsyncHandler asyncHandler; - private final NettyResponseFuture future; - - public ProgressListener(AsyncHttpClientConfig config,// - AsyncHandler asyncHandler,// - NettyResponseFuture future,// - boolean notifyHeaders) { - this.config = config; - this.asyncHandler = asyncHandler; - this.future = future; - this.notifyHeaders = notifyHeaders; - } - - private boolean abortOnThrowable(Throwable cause, Channel channel) { - if (cause != null && future.getState() != NettyResponseFuture.STATE.NEW) { - // The write operation failed. If the channel was cached, it means it got asynchronously closed. - // Let's retry a second time. - if (cause instanceof IllegalStateException || cause instanceof ClosedChannelException || StackTraceInspector.recoverOnReadOrWriteException(cause)) { - LOGGER.debug(cause == null ? "" : cause.getMessage(), cause); - Channels.silentlyCloseChannel(channel); - - } else { - future.abort(cause); - } - return true; - } - return false; - } - - public void operationComplete(ChannelFuture cf) { - - if (!abortOnThrowable(cf.getCause(), cf.getChannel())) { - 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) { - if (notifyHeaders) { - ProgressAsyncHandler.class.cast(asyncHandler).onHeadersWritten(); - } else { - ProgressAsyncHandler.class.cast(asyncHandler).onContentWritten(); - } - } - } - } - - public void operationProgressed(ChannelFuture cf, long amount, long current, long total) { - future.touch(); - if (asyncHandler instanceof ProgressAsyncHandler) { - ProgressAsyncHandler.class.cast(asyncHandler).onContentWriteProgress(amount, current, total); - } - } -} \ No newline at end of file diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java deleted file mode 100644 index 64158f1490..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java +++ /dev/null @@ -1,76 +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.netty.request.body; - -import java.nio.ByteBuffer; - -import org.asynchttpclient.request.body.Body; -import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.handler.stream.ChunkedInput; - -/** - * 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); - } - - public boolean hasNextChunk() throws Exception { - // unused - throw new UnsupportedOperationException(); - } - - public Object nextChunk() throws Exception { - if (endOfInput) { - return null; - } else { - 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 ChannelBuffers.wrappedBuffer(buffer); - } - } - } - - public boolean isEndOfInput() throws Exception { - // called by ChunkedWriteHandler AFTER nextChunk - return endOfInput; - } - - public void close() throws Exception { - body.close(); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java deleted file mode 100644 index b62b546bbb..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.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.netty.request.body; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; - -import java.io.IOException; -import java.nio.channels.WritableByteChannel; - -import org.asynchttpclient.request.body.RandomAccessBody; -import org.jboss.netty.channel.FileRegion; - -/** - * Adapts a {@link RandomAccessBody} to Netty's {@link FileRegion}. - */ -public class BodyFileRegion implements FileRegion { - - private final RandomAccessBody body; - - public BodyFileRegion(RandomAccessBody body) { - if (body == null) - throw new NullPointerException("body"); - this.body = body; - } - - public long getPosition() { - return 0; - } - - public long getCount() { - return body.getContentLength(); - } - - public long transferTo(WritableByteChannel target, long position) - throws IOException { - return body.transferTo(position, target); - } - - public void releaseExternalResources() { - closeSilently(body); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java deleted file mode 100644 index 53d33cc79d..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.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.netty.request.body; - -import java.io.IOException; - -import org.asynchttpclient.netty.NettyResponseFuture; -import org.jboss.netty.channel.Channel; - -public interface NettyBody { - - long getContentLength(); - - String getContentType(); - - void write(Channel channel, NettyResponseFuture future) throws IOException; -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java deleted file mode 100644 index 40ed0fe252..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java +++ /dev/null @@ -1,85 +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.netty.request.body; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; - -import java.io.IOException; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.ChannelManager; -import org.asynchttpclient.netty.request.ProgressListener; -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.FeedableBodyGenerator; -import org.asynchttpclient.request.body.generator.FeedableBodyGenerator.FeedListener; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.handler.stream.ChunkedWriteHandler; - -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 String getContentType() { - return null; - } - - @Override - public void write(final Channel channel, NettyResponseFuture future) throws IOException { - - Object msg; - if (body instanceof RandomAccessBody && !ChannelManager.isSslHandlerConfigured(channel.getPipeline()) && !config.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.getPipeline().get(ChunkedWriteHandler.class).resumeTransfer(); - } - }); - } - } - - channel.write(msg).addListener(new ProgressListener(config, future.getAsyncHandler(), future, false) { - public void operationComplete(ChannelFuture cf) { - closeSilently(body); - super.operationComplete(cf); - } - }); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java deleted file mode 100755 index 8e9be0ce2d..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.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.netty.request.body; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; - -public class NettyByteArrayBody extends NettyDirectBody { - - 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; - } - - @Override - public long getContentLength() { - return bytes.length; - } - - @Override - public String getContentType() { - return contentType; - } - - @Override - public ChannelBuffer channelBuffer() { - return ChannelBuffers.wrappedBuffer(bytes); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufferBody.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufferBody.java deleted file mode 100644 index 80621c6858..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufferBody.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2015 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.netty.request.body; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; - -import java.nio.ByteBuffer; - -public class NettyByteBufferBody extends NettyDirectBody { - - private final ByteBuffer bb; - private final String contentType; - private final long length; - - public NettyByteBufferBody(ByteBuffer bb) { - this(bb, null); - } - - public NettyByteBufferBody(ByteBuffer bb, String contentType) { - this.bb = bb; - length = bb.remaining(); - bb.mark(); - this.contentType = contentType; - } - - @Override - public long getContentLength() { - return length; - } - - @Override - public String getContentType() { - return contentType; - } - - @Override - public ChannelBuffer channelBuffer() { - // for retry - bb.reset(); - return ChannelBuffers.wrappedBuffer(bb); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.java deleted file mode 100644 index 21b2dc3f3d..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.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.netty.request.body; - -import java.util.List; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; - -public class NettyCompositeByteArrayBody extends NettyDirectBody { - - private final byte[][] bytes; - private final String contentType; - private final long contentLength; - - public NettyCompositeByteArrayBody(List bytes) { - this(bytes, null); - } - - public NettyCompositeByteArrayBody(List bytes, String contentType) { - this.bytes = new byte[bytes.size()][]; - bytes.toArray(this.bytes); - this.contentType = contentType; - long l = 0; - for (byte[] b : bytes) - l += b.length; - contentLength = l; - } - - @Override - public long getContentLength() { - return contentLength; - } - - @Override - public String getContentType() { - return contentType; - } - - @Override - public ChannelBuffer channelBuffer() { - return ChannelBuffers.wrappedBuffer(bytes); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.java deleted file mode 100644 index 8dcb8917ed..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.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.netty.request.body; - -import java.io.IOException; - -import org.asynchttpclient.netty.NettyResponseFuture; -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.channel.Channel; - -public abstract class NettyDirectBody implements NettyBody { - - public abstract ChannelBuffer channelBuffer(); - - @Override - public void write(Channel channel, NettyResponseFuture future) throws IOException { - throw new UnsupportedOperationException("This kind of body is supposed to be writen directly"); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java deleted file mode 100644 index 7954ac1b9e..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java +++ /dev/null @@ -1,93 +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.netty.request.body; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.ChannelManager; -import org.asynchttpclient.netty.request.ProgressListener; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.FileRegion; -import org.jboss.netty.handler.stream.ChunkedFile; - -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; - } - - 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) throws IOException { - final RandomAccessFile raf = new RandomAccessFile(file, "r"); - - try { - ChannelFuture writeFuture; - if (ChannelManager.isSslHandlerConfigured(channel.getPipeline()) || config.isDisableZeroCopy()) { - writeFuture = channel.write(new ChunkedFile(raf, offset, raf.length(), config.getChunkedFileChunkSize())); - } else { - final FileRegion region = new OptimizedFileRegion(raf, offset, raf.length()); - writeFuture = channel.write(region); - } - writeFuture.addListener(new ProgressListener(config, future.getAsyncHandler(), future, false) { - public void operationComplete(ChannelFuture cf) { - closeSilently(raf); - super.operationComplete(cf); - } - }); - } catch (IOException ex) { - closeSilently(raf); - throw ex; - } - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java deleted file mode 100644 index 570e37bbc2..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java +++ /dev/null @@ -1,96 +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.netty.request.body; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; - -import java.io.IOException; -import java.io.InputStream; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.request.ProgressListener; -import org.asynchttpclient.request.body.Body; -import org.asynchttpclient.request.body.generator.InputStreamBodyGenerator; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFuture; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class NettyInputStreamBody implements NettyBody { - - private static final Logger LOGGER = LoggerFactory.getLogger(NettyInputStreamBody.class); - - private final InputStream inputStream; - private final AsyncHttpClientConfig config; - - public NettyInputStreamBody(InputStream inputStream, AsyncHttpClientConfig config) { - this.inputStream = inputStream; - this.config = config; - } - - 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) 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); - } - - InputStreamBodyGenerator generator = new InputStreamBodyGenerator(is); - // FIXME is this still usefull? - generator.patchNetty3ChunkingIssue(true); - final Body body = generator.createBody(); - channel.write(new BodyChunkedInput(body)).addListener(new ProgressListener(config, future.getAsyncHandler(), future, false) { - public void operationComplete(ChannelFuture cf) { - closeSilently(body); - super.operationComplete(cf); - } - }); - - // FIXME ChunkedStream is broken in Netty 3 but fixed in Netty 4 -// channel.write(new ChunkedStream(is)).addListener( -// new ProgressListener(config, future.getAsyncHandler(), future, false) { -// public void operationComplete(ChannelFuture cf) { -// try { -// is.close(); -// } catch (IOException e) { -// LOGGER.warn("Failed to close request body: {}", e.getMessage(), e); -// } -// super.operationComplete(cf); -// } -// }); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java deleted file mode 100644 index 3990d1c85f..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java +++ /dev/null @@ -1,41 +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.netty.request.body; - -import java.util.List; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.request.body.multipart.MultipartBody; -import org.asynchttpclient.request.body.multipart.MultipartUtils; -import org.asynchttpclient.request.body.multipart.Part; - -public class NettyMultipartBody extends NettyBodyBody { - - private final String contentType; - - public NettyMultipartBody(List parts, FluentCaseInsensitiveStringsMap headers, AsyncHttpClientConfig config) { - this(MultipartUtils.newMultipartBody(parts, headers), config); - } - - private NettyMultipartBody(MultipartBody body, AsyncHttpClientConfig config) { - super(body, config); - contentType = body.getContentType(); - } - - @Override - public String getContentType() { - return contentType; - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/OptimizedFileRegion.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/OptimizedFileRegion.java deleted file mode 100644 index 66aa34affb..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/request/body/OptimizedFileRegion.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.netty.request.body; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; - -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; -import java.nio.channels.WritableByteChannel; - -import org.jboss.netty.channel.FileRegion; - -public class OptimizedFileRegion implements FileRegion { - - private final FileChannel file; - private final RandomAccessFile raf; - private final long position; - private final long count; - private long byteWritten; - - public OptimizedFileRegion(RandomAccessFile raf, long position, long count) { - this.raf = raf; - this.file = raf.getChannel(); - this.position = position; - this.count = count; - } - - public long getPosition() { - return position; - } - - public long getCount() { - return count; - } - - public long transferTo(WritableByteChannel target, long position) throws IOException { - long count = this.count - position; - if (count < 0 || position < 0) { - throw new IllegalArgumentException("position out of range: " + position + " (expected: 0 - " + (this.count - 1) + ")"); - } - if (count == 0) { - return 0L; - } - - long bw = file.transferTo(this.position + position, count, target); - byteWritten += bw; - if (byteWritten == raf.length()) { - releaseExternalResources(); - } - return bw; - } - - public void releaseExternalResources() { - closeSilently(file); - closeSilently(raf); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java deleted file mode 100644 index ac5d610b17..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.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.netty.timeout; - -import static org.asynchttpclient.util.DateUtils.millisTime; - -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.request.NettyRequestSender; -import org.jboss.netty.util.Timeout; - -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) { - // readTimeout 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; - } - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java deleted file mode 100644 index 4495abc7a1..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java +++ /dev/null @@ -1,49 +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.netty.timeout; - -import static org.asynchttpclient.util.DateUtils.millisTime; - -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.request.NettyRequestSender; -import org.jboss.netty.util.Timeout; - -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 readTimeout 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); - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java deleted file mode 100644 index 363a2ca0a6..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/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.netty.timeout; - -import java.net.SocketAddress; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.request.NettyRequestSender; -import org.jboss.netty.util.TimerTask; -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 - SocketAddress sa = nettyResponseFuture.getChannelRemoteAddress(); - remoteAddress = sa != null ? sa.toString() : "not-connected"; - } - - 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/netty3/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java deleted file mode 100644 index fbb60f6b24..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java +++ /dev/null @@ -1,39 +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.netty.timeout; - -import org.jboss.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.getTask()).clean(); - requestTimeout = null; - } - if (readTimeout != null) { - readTimeout.cancel(); - ReadTimeoutTimerTask.class.cast(readTimeout.getTask()).clean(); - readTimeout = null; - } - } - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/util/ChannelBufferUtils.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/util/ChannelBufferUtils.java deleted file mode 100644 index e3791f017e..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/util/ChannelBufferUtils.java +++ /dev/null @@ -1,33 +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.netty.util; - -import org.jboss.netty.buffer.ChannelBuffer; - -public class ChannelBufferUtils { - - public static byte[] channelBuffer2bytes(ChannelBuffer b) { - int readable = b.readableBytes(); - int readerIndex = b.readerIndex(); - if (b.hasArray()) { - byte[] array = b.array(); - if (b.arrayOffset() == 0 && readerIndex == 0 && array.length == readable) { - return array; - } - } - byte[] array = new byte[readable]; - b.getBytes(readerIndex, array); - return array; - } -} diff --git a/providers/netty3/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java b/providers/netty3/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java deleted file mode 100644 index 18d915b38d..0000000000 --- a/providers/netty3/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java +++ /dev/null @@ -1,320 +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.netty.ws; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.asynchttpclient.netty.util.ChannelBufferUtils.channelBuffer2bytes; -import static org.jboss.netty.buffer.ChannelBuffers.wrappedBuffer; - -import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.ConcurrentLinkedQueue; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.netty.NettyResponseBodyPart; -import org.asynchttpclient.ws.WebSocket; -import org.asynchttpclient.ws.WebSocketByteFragmentListener; -import org.asynchttpclient.ws.WebSocketByteListener; -import org.asynchttpclient.ws.WebSocketCloseCodeReasonListener; -import org.asynchttpclient.ws.WebSocketListener; -import org.asynchttpclient.ws.WebSocketPingListener; -import org.asynchttpclient.ws.WebSocketPongListener; -import org.asynchttpclient.ws.WebSocketTextFragmentListener; -import org.asynchttpclient.ws.WebSocketTextListener; -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFutureListener; -import org.jboss.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame; -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, AsyncHttpClientConfig config) { - this(channel, config, new ConcurrentLinkedQueue()); - } - - public NettyWebSocket(Channel channel, AsyncHttpClientConfig config, Collection listeners) { - this.channel = channel; - this.listeners = listeners; - maxBufferSize = config.getWebSocketMaxBufferSize(); - } - - @Override - public SocketAddress getRemoteAddress() { - return channel.getRemoteAddress(); - } - - @Override - public SocketAddress getLocalAddress() { - return channel.getLocalAddress(); - } - - @Override - public WebSocket sendMessage(byte[] message) { - channel.write(new BinaryWebSocketFrame(wrappedBuffer(message))); - return this; - } - - @Override - public WebSocket stream(byte[] fragment, boolean last) { - BinaryWebSocketFrame frame = new BinaryWebSocketFrame(wrappedBuffer(fragment)); - frame.setFinalFragment(last); - channel.write(frame); - return this; - } - - @Override - public WebSocket stream(byte[] fragment, int offset, int len, boolean last) { - BinaryWebSocketFrame frame = new BinaryWebSocketFrame(wrappedBuffer(fragment, offset, len)); - frame.setFinalFragment(last); - channel.write(frame); - return this; - } - - @Override - public WebSocket sendMessage(String message) { - channel.write(new TextWebSocketFrame(message)); - return this; - } - - @Override - public WebSocket stream(String fragment, boolean last) { - TextWebSocketFrame frame = new TextWebSocketFrame(fragment); - frame.setFinalFragment(last); - channel.write(frame); - return this; - } - - @Override - public WebSocket sendPing(byte[] payload) { - channel.write(new PingWebSocketFrame(wrappedBuffer(payload))); - return this; - } - - @Override - public WebSocket sendPong(byte[] payload) { - channel.write(new PongWebSocketFrame(wrappedBuffer(payload))); - return this; - } - - @Override - public boolean isOpen() { - return channel.isOpen(); - } - - @Override - public void close() { - if (channel.isOpen()) { - onClose(); - listeners.clear(); - channel.write(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(ChannelBuffer buffer) { - bufferSize += buffer.readableBytes(); - 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(ChannelBuffer channelBuffer) { - byte[] message = channelBuffer2bytes(channelBuffer); - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketByteListener) - WebSocketByteListener.class.cast(listener).onMessage(message); - } - } - - private void notifyTextListeners(ChannelBuffer channelBuffer) { - String message = channelBuffer.toString(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) { - ChannelBuffer fragment = NettyResponseBodyPart.class.cast(part).getChannelBuffer(); - - if (part.isLast()) { - if (bufferSize == 0) { - notifyByteListeners(fragment); - - } else { - bufferFragment(fragment); - notifyByteListeners(wrappedBuffer(fragments().toArray(new ChannelBuffer[fragments().size()]))); - } - - reset(); - - } else - bufferFragment(fragment); - } - } - - public void onTextFragment(HttpResponseBodyPart part) { - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketTextFragmentListener) - WebSocketTextFragmentListener.class.cast(listener).onFragment(part); - } - - if (interestedInTextMessages) { - ChannelBuffer fragment = NettyResponseBodyPart.class.cast(part).getChannelBuffer(); - - if (part.isLast()) { - if (bufferSize == 0) { - notifyTextListeners(fragment); - - } else { - bufferFragment(fragment); - notifyTextListeners(wrappedBuffer(fragments().toArray(new ChannelBuffer[fragments().size()]))); - } - - 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/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncHttpProviderTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncHttpProviderTest.java deleted file mode 100644 index 3e30b1df1a..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; - -public class NettyAsyncHttpProviderTest extends AbstractBasicTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncProviderBasicTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncProviderBasicTest.java deleted file mode 100644 index 75ed942fb6..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncProviderBasicTest.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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.AsyncProvidersBasicTest; -import org.testng.annotations.Test; - -@Test -public class NettyAsyncProviderBasicTest extends AsyncProvidersBasicTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - @Override - protected AsyncHttpProviderConfig getProviderConfig() { - return new NettyAsyncHttpProviderConfig().addProperty("tcpNoDelay", true); - } -} \ No newline at end of file diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncProviderPipelineTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncProviderPipelineTest.java deleted file mode 100644 index da04cf7fe7..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig.AdditionalPipelineInitializer; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.MessageEvent; -import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.handler.codec.http.HttpMessage; -import org.testng.annotations.Test; - -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.setHttpAdditionalPipelineInitializer(new AdditionalPipelineInitializer() { - public void initPipeline(ChannelPipeline pipeline) throws Exception { - pipeline.addBefore("inflater", "copyEncodingHeader", new CopyEncodingHandler()); - } - }); - - try (AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder() - .setAsyncHttpClientProviderConfig(nettyConfig).build())) { - 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"); - } - } - } - - private static class CopyEncodingHandler extends SimpleChannelUpstreamHandler { - @Override - public void messageReceived(final ChannelHandlerContext ctx, MessageEvent e) throws Exception { - - Object message = e.getMessage(); - - if (message instanceof HttpMessage) { - HttpMessage m = (HttpMessage) message; - // for test there is no Content-Encoding header so just hard - // coding value - // for verification - m.headers().set("X-Original-Content-Encoding", ""); - } - ctx.sendUpstream(e); - } - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java deleted file mode 100644 index 37ec10926b..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java +++ /dev/null @@ -1,91 +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.netty; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.cookie.Cookie; -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, null), new HttpResponseHeaders() { - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); - } - }, null); - - List cookies = response.getCookies(); - assertEquals(cookies.size(), 1); - - Cookie cookie = cookies.get(0); - assertTrue(cookie.getMaxAge() >= 58 && cookie.getMaxAge() <= 60); - } - - @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, null), new HttpResponseHeaders() { - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); - } - }, 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, null), new HttpResponseHeaders() { - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); - } - }, null); - - List cookies = response.getCookies(); - assertEquals(cookies.size(), 1); - - Cookie cookie = cookies.get(0); - assertEquals(cookie.getMaxAge(), Long.MIN_VALUE); - } - -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncStreamHandlerTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncStreamHandlerTest.java deleted file mode 100644 index e03efb1f40..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncStreamHandlerTest; - -public class NettyAsyncStreamHandlerTest extends AsyncStreamHandlerTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncStreamLifecycleTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAsyncStreamLifecycleTest.java deleted file mode 100644 index c66e9d250c..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncStreamLifecycleTest; - -public class NettyAsyncStreamLifecycleTest extends AsyncStreamLifecycleTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAuthTimeoutTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyAuthTimeoutTest.java deleted file mode 100644 index 89e25b215e..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AuthTimeoutTest; - -public class NettyAuthTimeoutTest extends AuthTimeoutTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyBasicAuthTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyBasicAuthTest.java deleted file mode 100644 index 0b9db54af8..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyBasicAuthTest.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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.BasicAuthTest; - -public class NettyBasicAuthTest extends BasicAuthTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyBasicHttpsTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyBasicHttpsTest.java deleted file mode 100644 index 9fe45ee2b7..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.BasicHttpsTest; - -public class NettyBasicHttpsTest extends BasicHttpsTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} \ No newline at end of file diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyByteBufferCapacityTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyByteBufferCapacityTest.java deleted file mode 100644 index e38a63e1eb..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ByteBufferCapacityTest; - -public class NettyByteBufferCapacityTest extends ByteBufferCapacityTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyComplexClientTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyComplexClientTest.java deleted file mode 100644 index d055f91317..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ComplexClientTest; - -public class NettyComplexClientTest extends ComplexClientTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyConnectionPoolTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyConnectionPoolTest.java deleted file mode 100644 index cae39841f0..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyConnectionPoolTest.java +++ /dev/null @@ -1,120 +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.netty; - -import static org.asynchttpclient.test.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 java.net.ConnectException; -import java.util.concurrent.TimeUnit; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.asynchttpclient.channel.pool.ConnectionPoolTest; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.netty.channel.pool.ChannelPool; -import org.asynchttpclient.netty.channel.pool.NoopChannelPool; -import org.jboss.netty.channel.Channel; -import org.testng.annotations.Test; - -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 NoopChannelPool() { - - @Override - public boolean offer(Channel connection, Object partitionKey) { - return false; - } - - @Override - public boolean isOpen() { - return false; - } - }; - - NettyAsyncHttpProviderConfig providerConfig = new NettyAsyncHttpProviderConfig(); - providerConfig.setChannelPool(cp); - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAsyncHttpClientProviderConfig(providerConfig) - .build())) { - Exception exception = null; - try { - client.prepareGet(getTargetUrl()).execute().get(TIMEOUT, TimeUnit.SECONDS); - } catch (Exception ex) { - ex.printStackTrace(); - exception = ex; - } - assertNotNull(exception); - assertNotNull(exception.getCause()); - assertEquals(exception.getCause().getMessage(), "Pool is already closed"); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testValidConnectionsPool() { - ChannelPool cp = new NoopChannelPool() { - - @Override - public boolean offer(Channel connection, Object partitionKey) { - return true; - } - }; - - NettyAsyncHttpProviderConfig providerConfig = new NettyAsyncHttpProviderConfig(); - providerConfig.setChannelPool(cp); - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAsyncHttpClientProviderConfig(providerConfig) - .build())) { - Exception exception = null; - try { - client.prepareGet(getTargetUrl()).execute().get(TIMEOUT, TimeUnit.SECONDS); - } catch (Exception ex) { - ex.printStackTrace(); - exception = ex; - } - assertNull(exception); - } - } - - @Test - public void testHostNotContactable() { - - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().build())) { - 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); - } - } - } - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyDigestAuthTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyDigestAuthTest.java deleted file mode 100644 index cc48a025c4..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.DigestAuthTest; - -public class NettyDigestAuthTest extends DigestAuthTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyErrorResponseTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyErrorResponseTest.java deleted file mode 100644 index b8a899ce4d..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ErrorResponseTest; - -public class NettyErrorResponseTest extends ErrorResponseTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyExpect100ContinueTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyExpect100ContinueTest.java deleted file mode 100644 index 6a5a6ce8f4..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Expect100ContinueTest; - -public class NettyExpect100ContinueTest extends Expect100ContinueTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyFollowingThreadTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyFollowingThreadTest.java deleted file mode 100644 index 43d6707fc8..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FollowingThreadTest; - -public class NettyFollowingThreadTest extends FollowingThreadTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyHead302Test.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyHead302Test.java deleted file mode 100644 index 43f5a0f6d2..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Head302Test; - -public class NettyHead302Test extends Head302Test { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyHttpToHttpsRedirectTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyHttpToHttpsRedirectTest.java deleted file mode 100644 index 8f3d54d8c8..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpToHttpsRedirectTest; - -public class NettyHttpToHttpsRedirectTest extends HttpToHttpsRedirectTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyIdleStateHandlerTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyIdleStateHandlerTest.java deleted file mode 100644 index 590eadb39b..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.IdleStateHandlerTest; - -public class NettyIdleStateHandlerTest extends IdleStateHandlerTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyMultipleHeaderTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyMultipleHeaderTest.java deleted file mode 100644 index cad2dcbde1..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.MultipleHeaderTest; - -public class NettyMultipleHeaderTest extends MultipleHeaderTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyNoNullResponseTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyNoNullResponseTest.java deleted file mode 100644 index 4e133dd514..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.NoNullResponseTest; - -public class NettyNoNullResponseTest extends NoNullResponseTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyNonAsciiContentLengthTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyNonAsciiContentLengthTest.java deleted file mode 100644 index 193ccec42f..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.NonAsciiContentLengthTest; - -public class NettyNonAsciiContentLengthTest extends NonAsciiContentLengthTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyParamEncodingTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyParamEncodingTest.java deleted file mode 100644 index 0315db8cda..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ParamEncodingTest; - -public class NettyParamEncodingTest extends ParamEncodingTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyPerRequestRelative302Test.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyPerRequestRelative302Test.java deleted file mode 100644 index 23ef73899e..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.PerRequestRelative302Test; - -public class NettyPerRequestRelative302Test extends PerRequestRelative302Test { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyPerRequestTimeoutTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyPerRequestTimeoutTest.java deleted file mode 100644 index 41b8f1bbf3..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.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/netty3/src/test/java/org/asynchttpclient/netty/NettyPostRedirectGetTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyPostRedirectGetTest.java deleted file mode 100644 index 18fd29e3ce..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.PostRedirectGetTest; - -public class NettyPostRedirectGetTest extends PostRedirectGetTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyPostWithQSTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyPostWithQSTest.java deleted file mode 100644 index d7bc0eaf72..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.PostWithQSTest; - -public class NettyPostWithQSTest extends PostWithQSTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyProviderUtil.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyProviderUtil.java deleted file mode 100644 index d889242c55..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyProviderUtil.java +++ /dev/null @@ -1,31 +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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.DefaultAsyncHttpClient; -import org.asynchttpclient.netty.NettyAsyncHttpProvider; - -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/netty3/src/test/java/org/asynchttpclient/netty/NettyQueryParametersTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyQueryParametersTest.java deleted file mode 100644 index 77a63d4d0d..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.QueryParametersTest; - -public class NettyQueryParametersTest extends QueryParametersTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRC10KTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRC10KTest.java deleted file mode 100644 index 276d21f7fe..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.RC10KTest; - -public class NettyRC10KTest extends RC10KTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRedirectConnectionUsageTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRedirectConnectionUsageTest.java deleted file mode 100644 index f7f3846714..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.RedirectConnectionUsageTest; - -public class NettyRedirectConnectionUsageTest extends RedirectConnectionUsageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRelative302Test.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRelative302Test.java deleted file mode 100644 index 5bc48614ff..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Relative302Test; - -public class NettyRelative302Test extends Relative302Test { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRemoteSiteTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRemoteSiteTest.java deleted file mode 100644 index f15c322564..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.RemoteSiteTest; - -public class NettyRemoteSiteTest extends RemoteSiteTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRequestThrottleTimeoutTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRequestThrottleTimeoutTest.java deleted file mode 100644 index e73980600a..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRequestThrottleTimeoutTest.java +++ /dev/null @@ -1,143 +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.netty; - -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -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.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); - - int samples = 10; - - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setMaxConnections(1).build())) { - 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()).setRequestTimeout(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"); - } - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRetryRequestTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRetryRequestTest.java deleted file mode 100644 index 1fdeaa8385..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/NettyRetryRequestTest.java +++ /dev/null @@ -1,27 +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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.RetryRequestTest; - -public class NettyRetryRequestTest extends RetryRequestTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/RetryNonBlockingIssue.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/RetryNonBlockingIssue.java deleted file mode 100644 index bc1e2a1fd8..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/RetryNonBlockingIssue.java +++ /dev/null @@ -1,216 +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.netty; - -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.AbstractBasicTest; -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.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)// - .setConnectTimeout(60000)// - .setRequestTimeout(30000)// - .build(); - - try (AsyncHttpClient client = getAsyncHttpClient(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"); - 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(); - } - } - - @Test - public void testRetryNonBlockingAsyncConnect() throws IOException, InterruptedException, ExecutionException { - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setAllowPoolingConnections(true)// - .setMaxConnections(100)// - .setConnectTimeout(60000)// - .setRequestTimeout(30000)// - .build(); - - try (AsyncHttpClient client = getAsyncHttpClient(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"); - 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(); - } - } - - @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/netty3/src/test/java/org/asynchttpclient/netty/channel/NettyMaxConnectionsInThreads.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/channel/NettyMaxConnectionsInThreads.java deleted file mode 100644 index 55a1da6876..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/channel/NettyMaxConnectionsInThreads.java +++ /dev/null @@ -1,24 +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.netty.channel; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.channel.MaxConnectionsInThreads; -import org.asynchttpclient.netty.NettyProviderUtil; - -public class NettyMaxConnectionsInThreads extends MaxConnectionsInThreads { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/channel/NettyMaxTotalConnectionTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/channel/NettyMaxTotalConnectionTest.java deleted file mode 100644 index e8faf39900..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/channel/NettyMaxTotalConnectionTest.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.netty.channel; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.channel.MaxTotalConnectionTest; -import org.asynchttpclient.netty.NettyProviderUtil; - -public class NettyMaxTotalConnectionTest extends MaxTotalConnectionTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/filter/NettyFilterTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/filter/NettyFilterTest.java deleted file mode 100644 index a1854eea14..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/filter/NettyFilterTest.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.netty.filter; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.filter.FilterTest; -import org.asynchttpclient.netty.NettyProviderUtil; -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/netty3/src/test/java/org/asynchttpclient/netty/future/NettyListenableFutureTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/future/NettyListenableFutureTest.java deleted file mode 100644 index d71c19122c..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/future/NettyListenableFutureTest.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.netty.future; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ListenableFutureTest; -import org.asynchttpclient.netty.NettyProviderUtil; - -public class NettyListenableFutureTest extends ListenableFutureTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/handler/NettyBodyDeferringAsyncHandlerTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/handler/NettyBodyDeferringAsyncHandlerTest.java deleted file mode 100644 index d3875b70af..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/handler/NettyBodyDeferringAsyncHandlerTest.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.netty.handler; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.handler.BodyDeferringAsyncHandlerTest; -import org.asynchttpclient.netty.NettyProviderUtil; - -public class NettyBodyDeferringAsyncHandlerTest extends BodyDeferringAsyncHandlerTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/ntlm/NettyNtlmTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/ntlm/NettyNtlmTest.java deleted file mode 100644 index 48a97d0e36..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/ntlm/NettyNtlmTest.java +++ /dev/null @@ -1,29 +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.netty.ntlm; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.ntlm.NtlmTest; -import org.testng.annotations.Test; - -@Test -public class NettyNtlmTest extends NtlmTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/proxy/NettyProxyTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/proxy/NettyProxyTest.java deleted file mode 100644 index 43c37e567f..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/proxy/NettyProxyTest.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.netty.proxy; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.proxy.ProxyTest; - -public class NettyProxyTest extends ProxyTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/proxy/NettyProxyTunnellingTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/proxy/NettyProxyTunnellingTest.java deleted file mode 100644 index 1086a91b65..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/proxy/NettyProxyTunnellingTest.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.netty.proxy; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.proxy.ProxyTunnellingTest; - -public class NettyProxyTunnellingTest extends ProxyTunnellingTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyBodyChunkTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyBodyChunkTest.java deleted file mode 100644 index 9a0f316d50..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyBodyChunkTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.BodyChunkTest; - -public class NettyBodyChunkTest extends BodyChunkTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyChunkingTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyChunkingTest.java deleted file mode 100644 index 02a756c96a..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyChunkingTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.asynchttpclient.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.ChunkingTest; - -public class NettyChunkingTest extends ChunkingTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyEmptyBodyTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyEmptyBodyTest.java deleted file mode 100644 index 52c8464546..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyEmptyBodyTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.EmptyBodyTest; - -public class NettyEmptyBodyTest extends EmptyBodyTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyFastUnauthorizedUploadTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyFastUnauthorizedUploadTest.java deleted file mode 100644 index 9b6eae7ad5..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyFastUnauthorizedUploadTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.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/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyFilePartLargeFileTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyFilePartLargeFileTest.java deleted file mode 100644 index 6265c04e44..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyFilePartLargeFileTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.FilePartLargeFileTest; - -public class NettyFilePartLargeFileTest extends FilePartLargeFileTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyInputStreamTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyInputStreamTest.java deleted file mode 100644 index 075a935fed..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyInputStreamTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.InputStreamTest; - -public class NettyInputStreamTest extends InputStreamTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyPutLargeFileTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyPutLargeFileTest.java deleted file mode 100644 index e1e3394da1..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyPutLargeFileTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.PutLargeFileTest; - -public class NettyPutLargeFileTest extends PutLargeFileTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyTransferListenerTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyTransferListenerTest.java deleted file mode 100644 index 32c093167c..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyTransferListenerTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.TransferListenerTest; -import org.testng.annotations.Test; - -@Test -public class NettyTransferListenerTest extends TransferListenerTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyZeroCopyFileTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyZeroCopyFileTest.java deleted file mode 100644 index 6db5e35735..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/NettyZeroCopyFileTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.ZeroCopyFileTest; - -public class NettyZeroCopyFileTest extends ZeroCopyFileTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/multipart/NettyMultipartUploadTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/multipart/NettyMultipartUploadTest.java deleted file mode 100644 index 2b2454b595..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/request/body/multipart/NettyMultipartUploadTest.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.netty.request.body.multipart; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.multipart.MultipartUploadTest; - -/** - * @author dominict - */ -public class NettyMultipartUploadTest extends MultipartUploadTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/simple/NettySimpleAsyncClientErrorBehaviourTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/simple/NettySimpleAsyncClientErrorBehaviourTest.java deleted file mode 100644 index 1a4e965d46..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/simple/NettySimpleAsyncClientErrorBehaviourTest.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.netty.simple; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.simple.SimpleAsyncClientErrorBehaviourTest; - -public class NettySimpleAsyncClientErrorBehaviourTest extends SimpleAsyncClientErrorBehaviourTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/simple/NettySimpleAsyncHttpClientTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/simple/NettySimpleAsyncHttpClientTest.java deleted file mode 100644 index bf36ef8484..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/simple/NettySimpleAsyncHttpClientTest.java +++ /dev/null @@ -1,20 +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.netty.simple; - -import org.asynchttpclient.simple.SimpleAsyncHttpClientTest; -import org.testng.annotations.Test; - -@Test -public class NettySimpleAsyncHttpClientTest extends SimpleAsyncHttpClientTest { -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/webdav/NettyWebDavBasicTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/webdav/NettyWebDavBasicTest.java deleted file mode 100644 index d7512671fb..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/webdav/NettyWebDavBasicTest.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.netty.webdav; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.webdav.WebDavBasicTest; - -public class NettyWebDavBasicTest extends WebDavBasicTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/NettyByteMessageTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/NettyByteMessageTest.java deleted file mode 100644 index e93fcdf4aa..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/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.netty.ws; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.ws.ByteMessageTest; - -public class NettyByteMessageTest extends ByteMessageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/NettyCloseCodeReasonMsgTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/NettyCloseCodeReasonMsgTest.java deleted file mode 100644 index 51feb24517..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/NettyCloseCodeReasonMsgTest.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.netty.ws; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.ws.CloseCodeReasonMessageTest; - -public class NettyCloseCodeReasonMsgTest extends CloseCodeReasonMessageTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/NettyProxyTunnellingTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/NettyProxyTunnellingTest.java deleted file mode 100644 index b5749300ab..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/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.netty.ws; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.ws.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/netty3/src/test/java/org/asynchttpclient/netty/ws/NettyRedirectTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/NettyRedirectTest.java deleted file mode 100644 index 8afa593932..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/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.netty.ws; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.ws.RedirectTest; - -public class NettyRedirectTest extends RedirectTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/NettyTextMessageTest.java b/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/NettyTextMessageTest.java deleted file mode 100644 index 6c351283c2..0000000000 --- a/providers/netty3/src/test/java/org/asynchttpclient/netty/ws/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.netty.ws; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.ws.TextMessageTest; - -public class NettyTextMessageTest extends TextMessageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty3/src/test/resources/300k.png b/providers/netty3/src/test/resources/300k.png deleted file mode 100644 index bff4a85989..0000000000 Binary files a/providers/netty3/src/test/resources/300k.png and /dev/null differ diff --git a/providers/netty3/src/test/resources/SimpleTextFile.txt b/providers/netty3/src/test/resources/SimpleTextFile.txt deleted file mode 100644 index 088788f821..0000000000 --- a/providers/netty3/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/netty3/src/test/resources/client.keystore b/providers/netty3/src/test/resources/client.keystore deleted file mode 100644 index eaf8339f44..0000000000 Binary files a/providers/netty3/src/test/resources/client.keystore and /dev/null differ diff --git a/providers/netty3/src/test/resources/gzip.txt.gz b/providers/netty3/src/test/resources/gzip.txt.gz deleted file mode 100644 index 80aeb98d2b..0000000000 Binary files a/providers/netty3/src/test/resources/gzip.txt.gz and /dev/null differ diff --git a/providers/netty3/src/test/resources/logback-test.xml b/providers/netty3/src/test/resources/logback-test.xml deleted file mode 100644 index 4acf278710..0000000000 --- a/providers/netty3/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/netty3/src/test/resources/realm.properties b/providers/netty3/src/test/resources/realm.properties deleted file mode 100644 index bc9faad66a..0000000000 --- a/providers/netty3/src/test/resources/realm.properties +++ /dev/null @@ -1 +0,0 @@ -user=admin, admin \ No newline at end of file diff --git a/providers/netty3/src/test/resources/ssltest-cacerts.jks b/providers/netty3/src/test/resources/ssltest-cacerts.jks deleted file mode 100644 index 207b9646e6..0000000000 Binary files a/providers/netty3/src/test/resources/ssltest-cacerts.jks and /dev/null differ diff --git a/providers/netty3/src/test/resources/ssltest-keystore.jks b/providers/netty3/src/test/resources/ssltest-keystore.jks deleted file mode 100644 index 70267836e8..0000000000 Binary files a/providers/netty3/src/test/resources/ssltest-keystore.jks and /dev/null differ diff --git a/providers/netty3/src/test/resources/textfile.txt b/providers/netty3/src/test/resources/textfile.txt deleted file mode 100644 index 87daee60a9..0000000000 --- a/providers/netty3/src/test/resources/textfile.txt +++ /dev/null @@ -1 +0,0 @@ -filecontent: hello \ No newline at end of file diff --git a/providers/netty3/src/test/resources/textfile2.txt b/providers/netty3/src/test/resources/textfile2.txt deleted file mode 100644 index 6a91fe609c..0000000000 --- a/providers/netty3/src/test/resources/textfile2.txt +++ /dev/null @@ -1 +0,0 @@ -filecontent: hello2 \ No newline at end of file diff --git a/providers/netty4/pom.xml b/providers/netty4/pom.xml deleted file mode 100644 index 5879de1873..0000000000 --- a/providers/netty4/pom.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - org.asynchttpclient - async-http-client-providers-parent - 2.0.0-SNAPSHOT - - 4.0.0 - async-http-client-netty4 - Asynchronous Http Client Netty 4 Provider - - The Async Http Client Netty 4 Provider. - - - - - io.netty - netty-codec-http - 4.0.30.Final - - - org.javassist - javassist - 3.20.0-GA - - - com.jcraft - jzlib - 1.1.3 - - - diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/Callback.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/Callback.java deleted file mode 100644 index 2e4393f851..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/Callback.java +++ /dev/null @@ -1,29 +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.netty; - - -public abstract class Callback { - - protected 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/netty4/src/main/java/org/asynchttpclient/netty/EagerNettyResponseBodyPart.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/EagerNettyResponseBodyPart.java deleted file mode 100755 index 3261473af1..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/EagerNettyResponseBodyPart.java +++ /dev/null @@ -1,67 +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.netty; - -import static org.asynchttpclient.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/netty4/src/main/java/org/asynchttpclient/netty/LazyNettyResponseBodyPart.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/LazyNettyResponseBodyPart.java deleted file mode 100755 index d07b08770a..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/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.netty; - -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/netty4/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProvider.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProvider.java deleted file mode 100755 index 65c5888445..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProvider.java +++ /dev/null @@ -1,94 +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.netty; - -import io.netty.util.HashedWheelTimer; -import io.netty.util.Timer; - -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.netty.channel.ChannelManager; -import org.asynchttpclient.netty.channel.pool.ChannelPoolPartitionSelector; -import org.asynchttpclient.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 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) { - - NettyAsyncHttpProviderConfig 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, 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) { - try { - return requestSender.sendRequest(request, asyncHandler, null, false); - } catch (Exception e) { - asyncHandler.onThrowable(e); - return new ListenableFuture.CompletedFailure<>(e); - } - } - - public void flushChannelPoolPartition(String partitionId) { - channelManager.flushPartition(partitionId); - } - - public void flushChannelPoolPartitions(ChannelPoolPartitionSelector selector) { - channelManager.flushPartitions(selector); - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProviderConfig.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProviderConfig.java deleted file mode 100755 index dfa4f2f84c..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyAsyncHttpProviderConfig.java +++ /dev/null @@ -1,221 +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.netty; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -import io.netty.channel.ChannelOption; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.EventLoopGroup; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.util.Timer; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.channel.pool.ConnectionStrategy; -import org.asynchttpclient.netty.channel.pool.ChannelPool; -import org.asynchttpclient.netty.handler.DefaultConnectionStrategy; -import org.asynchttpclient.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<>(); - - /** - * 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 AdditionalPipelineInitializer { - - void initPipeline(ChannelPipeline pipeline) 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, AsyncHttpClientConfig config); - } - - public class DefaultNettyWebSocketFactory implements NettyWebSocketFactory { - - @Override - public NettyWebSocket newNettyWebSocket(Channel channel, AsyncHttpClientConfig config) { - return new NettyWebSocket(channel, config); - } - } - - /** - * Allow configuring the Netty's event loop. - */ - private EventLoopGroup eventLoopGroup; - - private Class socketChannelClass; - - private AdditionalPipelineInitializer httpAdditionalPipelineInitializer; - private AdditionalPipelineInitializer wsAdditionalPipelineInitializer; - - private ResponseBodyPartFactory bodyPartFactory = new EagerResponseBodyPartFactory(); - - private ChannelPool channelPool; - - private Timer nettyTimer; - - private NettyWebSocketFactory nettyWebSocketFactory = new DefaultNettyWebSocketFactory(); - - private ConnectionStrategy connectionStrategy = new DefaultConnectionStrategy(); - - public EventLoopGroup getEventLoopGroup() { - return eventLoopGroup; - } - - public void setEventLoopGroup(EventLoopGroup eventLoopGroup) { - this.eventLoopGroup = eventLoopGroup; - } - - public Class getSocketChannelClass() { - return socketChannelClass; - } - - public void setSocketChannelClass(Class socketChannelClass) { - this.socketChannelClass = socketChannelClass; - } - - public AdditionalPipelineInitializer getHttpAdditionalPipelineInitializer() { - return httpAdditionalPipelineInitializer; - } - - public void setHttpAdditionalPipelineInitializer(AdditionalPipelineInitializer httpAdditionalPipelineInitializer) { - this.httpAdditionalPipelineInitializer = httpAdditionalPipelineInitializer; - } - - public AdditionalPipelineInitializer getWsAdditionalPipelineInitializer() { - return wsAdditionalPipelineInitializer; - } - - public void setWsAdditionalPipelineInitializer(AdditionalPipelineInitializer wsAdditionalPipelineInitializer) { - this.wsAdditionalPipelineInitializer = wsAdditionalPipelineInitializer; - } - - 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 Timer getNettyTimer() { - return nettyTimer; - } - - public void setNettyTimer(Timer nettyTimer) { - this.nettyTimer = nettyTimer; - } - - public NettyWebSocketFactory getNettyWebSocketFactory() { - return nettyWebSocketFactory; - } - - public void setNettyWebSocketFactory(NettyWebSocketFactory nettyWebSocketFactory) { - this.nettyWebSocketFactory = nettyWebSocketFactory; - } - - public ConnectionStrategy getConnectionStrategy() { - return connectionStrategy; - } - - public void setConnectionStrategy(ConnectionStrategy connectionStrategy) { - this.connectionStrategy = connectionStrategy; - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyResponse.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyResponse.java deleted file mode 100755 index 5d6bb29e3f..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyResponse.java +++ /dev/null @@ -1,100 +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.netty; - -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.nio.charset.Charset; -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.ResponseBase; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.cookie.CookieDecoder; - -/** - * Wrapper around the {@link org.asynchttpclient.Response} API. - */ -public class NettyResponse extends ResponseBase { - - public NettyResponse(HttpResponseStatus status,// - HttpResponseHeaders headers,// - List bodyParts) { - super(status, headers, bodyParts); - } - - 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); - 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(Charset charset) throws IOException { - return new String(getResponseBodyAsBytes(), calculateCharset(charset)); - } - - @Override - public InputStream getResponseBodyAsStream() throws IOException { - return new ByteArrayInputStream(getResponseBodyAsBytes()); - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyResponseBodyPart.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyResponseBodyPart.java deleted file mode 100755 index a17eb1e62d..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/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.netty; - -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/netty4/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java deleted file mode 100755 index 5c0573c37d..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java +++ /dev/null @@ -1,445 +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.netty; - -import static org.asynchttpclient.util.DateUtils.millisTime; -import io.netty.channel.Channel; -import io.netty.handler.codec.http.HttpHeaders; - -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.Request; -import org.asynchttpclient.channel.pool.ConnectionPoolPartitioning; -import org.asynchttpclient.future.AbstractListenableFuture; -import org.asynchttpclient.netty.channel.Channels; -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; - -/** - * 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 final long start = millisTime(); - private final ConnectionPoolPartitioning connectionPoolPartitioning; - 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 boolean keepAlive = true; - private Request request; - private NettyRequest nettyRequest; - private HttpHeaders httpHeaders; - private AsyncHandler asyncHandler; - private boolean streamWasAlreadyConsumed; - private boolean reuseChannel; - private boolean headersAlreadyWrittenOnContinue; - private boolean dontWriteBodyBecauseExpectContinue; - private boolean allowConnect; - - public NettyResponseFuture(Request request,// - AsyncHandler asyncHandler,// - NettyRequest nettyRequest,// - int maxRetry,// - ConnectionPoolPartitioning connectionPoolPartitioning,// - ProxyServer proxyServer) { - - this.asyncHandler = asyncHandler; - this.request = request; - this.nettyRequest = nettyRequest; - this.connectionPoolPartitioning = connectionPoolPartitioning; - this.proxyServer = proxyServer; - this.maxRetry = maxRetry; - } - - /*********************************************/ - /** java.util.concurrent.Future **/ - /*********************************************/ - - @Override - public boolean isDone() { - return isDone.get() || isCancelled(); - } - - @Override - public boolean isCancelled() { - return isCancelled.get(); - } - - @Override - public boolean cancel(boolean force) { - cancelTimeouts(); - - if (isCancelled.getAndSet(true)) - return false; - - // cancel could happen before channel was attached - if (channel != null) { - Channels.setDiscard(channel); - Channels.silentlyCloseChannel(channel); - } - - 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 { - - if (isCancelled()) - throw new CancellationException(); - - 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 **/ - /*********************************************/ - - private boolean terminateAndExit() { - cancelTimeouts(); - this.channel = null; - this.reuseChannel = false; - return isDone.getAndSet(true) || isCancelled.get(); - } - - public final void done() { - - if (terminateAndExit()) - 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) { - - exEx.compareAndSet(null, new ExecutionException(t)); - - if (terminateAndExit()) - return; - - 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 Uri getUri() { - return request.getUri(); - } - - public ConnectionPoolPartitioning getConnectionPoolPartitioning() { - return connectionPoolPartitioning; - } - - public ProxyServer getProxyServer() { - return proxyServer; - } - - public void setAsyncHandler(AsyncHandler asyncHandler) { - this.asyncHandler = asyncHandler; - } - - 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 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) { - - // future could have been cancelled first - if (isDone()) { - Channels.silentlyCloseChannel(channel); - } - - this.channel = channel; - this.reuseChannel = reuseChannel; - } - - public Channel channel() { - return channel; - } - - public boolean reuseChannel() { - return reuseChannel; - } - - public boolean canRetry() { - return maxRetry > 0 && currentRetry.incrementAndGet() <= maxRetry; - } - - 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) && !getUri().getScheme().equalsIgnoreCase("https")) && !isInAuth(); - } - - public long getStart() { - return start; - } - - public Object getPartitionKey() { - return connectionPoolPartitioning.getPartitionKey(request.getUri(), request.getVirtualHost(), proxyServer); - } - - @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=" + getUri() + // - ",\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/netty4/src/main/java/org/asynchttpclient/netty/NettyResponseHeaders.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyResponseHeaders.java deleted file mode 100755 index d87272d39d..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/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.netty; - -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/netty4/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java deleted file mode 100755 index 3de33a95ff..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/NettyResponseStatus.java +++ /dev/null @@ -1,102 +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.netty; - -import io.netty.channel.Channel; -import io.netty.handler.codec.http.HttpResponse; - -import java.net.SocketAddress; -import java.util.List; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; -import org.asynchttpclient.uri.Uri; - -/** - * 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, AsyncHttpClientConfig config, HttpResponse response, Channel channel) { - super(uri, config); - this.response = response; - if (channel != null) { - remoteAddress = channel.remoteAddress(); - localAddress = channel.localAddress(); - } else { - remoteAddress = null; - localAddress = null; - } - } - - @Override - public Response prepareResponse(HttpResponseHeaders headers, List bodyParts) { - return new NettyResponse(this, headers, bodyParts); - } - - /** - * 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(); - } - - @Override - public SocketAddress getRemoteAddress() { - return remoteAddress; - } - - @Override - public SocketAddress getLocalAddress() { - return localAddress; - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java deleted file mode 100755 index 60be40eb35..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java +++ /dev/null @@ -1,452 +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.netty.channel; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getExplicitPort; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getSchemeDefaultPort; -import static org.asynchttpclient.util.HttpUtils.WS; -import static org.asynchttpclient.util.HttpUtils.isSecure; -import static org.asynchttpclient.util.HttpUtils.isWebSocket; -import static org.asynchttpclient.util.MiscUtils.buildStaticIOException; -import io.netty.bootstrap.Bootstrap; -import io.netty.buffer.PooledByteBufAllocator; -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.oio.OioEventLoopGroup; -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 io.netty.util.internal.chmv8.ConcurrentHashMapV8; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.Map.Entry; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.net.ssl.SSLEngine; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.channel.SSLEngineFactory; -import org.asynchttpclient.channel.pool.ConnectionPoolPartitioning; -import org.asynchttpclient.handler.AsyncHandlerExtensions; -import org.asynchttpclient.netty.Callback; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.pool.ChannelPool; -import org.asynchttpclient.netty.channel.pool.ChannelPoolPartitionSelector; -import org.asynchttpclient.netty.channel.pool.DefaultChannelPool; -import org.asynchttpclient.netty.channel.pool.NoopChannelPool; -import org.asynchttpclient.netty.handler.HttpProtocol; -import org.asynchttpclient.netty.handler.Processor; -import org.asynchttpclient.netty.handler.WebSocketProtocol; -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 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 SSLEngineFactory sslEngineFactory; - private final EventLoopGroup eventLoopGroup; - private final boolean allowReleaseEventLoopGroup; - private final Class socketChannelClass; - private final Bootstrap httpBootstrap; - private final Bootstrap wsBootstrap; - private final long handshakeTimeout; - private final IOException tooManyConnections; - private final IOException tooManyConnectionsPerHost; - private final IOException poolAlreadyClosed; - - private final ChannelPool channelPool; - private final boolean maxTotalConnectionsEnabled; - private final Semaphore freeChannels; - private final ChannelGroup openChannels; - private final boolean maxConnectionsPerHostEnabled; - private final ConcurrentHashMapV8 freeChannelsPerHost; - private final ConcurrentHashMapV8 channelId2PartitionKey; - private final ConcurrentHashMapV8.Fun semaphoreComputer; - - private Processor wsProcessor; - - public ChannelManager(final AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, Timer nettyTimer) { - - this.config = config; - this.nettyConfig = nettyConfig; - this.sslEngineFactory = config.getSslEngineFactory() != null? config.getSslEngineFactory() : new SSLEngineFactory.DefaultSSLEngineFactory(config); - - ChannelPool channelPool = nettyConfig.getChannelPool(); - if (channelPool == null && config.isAllowPoolingConnections()) { - channelPool = new DefaultChannelPool(config, nettyTimer); - } else if (channelPool == null) { - channelPool = new NoopChannelPool(); - } - this.channelPool = channelPool; - - tooManyConnections = buildStaticIOException(String.format("Too many connections %s", config.getMaxConnections())); - tooManyConnectionsPerHost = buildStaticIOException(String.format("Too many connections per host %s", config.getMaxConnectionsPerHost())); - poolAlreadyClosed = buildStaticIOException("Pool is already closed"); - maxTotalConnectionsEnabled = config.getMaxConnections() > 0; - maxConnectionsPerHostEnabled = config.getMaxConnectionsPerHost() > 0; - - if (maxTotalConnectionsEnabled || maxConnectionsPerHostEnabled) { - openChannels = new CleanupChannelGroup("asyncHttpClient") { - @Override - public boolean remove(Object o) { - boolean removed = super.remove(o); - if (removed) { - if (maxTotalConnectionsEnabled) - freeChannels.release(); - if (maxConnectionsPerHostEnabled) { - Object partitionKey = channelId2PartitionKey.remove(Channel.class.cast(o)); - if (partitionKey != null) { - Semaphore freeChannelsForHost = freeChannelsPerHost.get(partitionKey); - if (freeChannelsForHost != null) - freeChannelsForHost.release(); - } - } - } - return removed; - } - }; - freeChannels = new Semaphore(config.getMaxConnections()); - } else { - openChannels = new CleanupChannelGroup("asyncHttpClient"); - freeChannels = null; - } - - if (maxConnectionsPerHostEnabled) { - freeChannelsPerHost = new ConcurrentHashMapV8<>(); - channelId2PartitionKey = new ConcurrentHashMapV8<>(); - semaphoreComputer = new ConcurrentHashMapV8.Fun() { - @Override - public Semaphore apply(Object partitionKey) { - return new Semaphore(config.getMaxConnectionsPerHost()); - } - }; - } else { - freeChannelsPerHost = null; - channelId2PartitionKey = null; - semaphoreComputer = null; - } - - handshakeTimeout = config.getHandshakeTimeout(); - - // check if external EventLoopGroup is defined - allowReleaseEventLoopGroup = nettyConfig.getEventLoopGroup() == null; - eventLoopGroup = allowReleaseEventLoopGroup ? new NioEventLoopGroup() : nettyConfig.getEventLoopGroup(); - if (eventLoopGroup instanceof OioEventLoopGroup) - throw new IllegalArgumentException("Oio is not supported"); - - // allow users to specify SocketChannel class and default to NioSocketChannel - socketChannelClass = nettyConfig.getSocketChannelClass() == null ? NioSocketChannel.class : nettyConfig.getSocketChannelClass(); - - httpBootstrap = new Bootstrap().channel(socketChannelClass).group(eventLoopGroup); - wsBootstrap = new Bootstrap().channel(socketChannelClass).group(eventLoopGroup); - - // default to PooledByteBufAllocator - httpBootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); - wsBootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); - - if (config.getConnectTimeout() > 0) - nettyConfig.addChannelOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectTimeout()); - for (Entry, Object> entry : nettyConfig.propertiesSet()) { - ChannelOption key = entry.getKey(); - Object value = entry.getValue(); - httpBootstrap.option(key, value); - wsBootstrap.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); - - httpBootstrap.handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline()// - .addLast(HTTP_HANDLER, newHttpClientCodec())// - .addLast(INFLATER_HANDLER, newHttpContentDecompressor())// - .addLast(CHUNKED_WRITER_HANDLER, new ChunkedWriteHandler())// - .addLast(HTTP_PROCESSOR, httpProcessor); - - if (nettyConfig.getHttpAdditionalPipelineInitializer() != null) - nettyConfig.getHttpAdditionalPipelineInitializer().initPipeline(ch.pipeline()); - } - }); - - wsBootstrap.handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline()// - .addLast(HTTP_HANDLER, newHttpClientCodec())// - .addLast(WS_PROCESSOR, wsProcessor); - - if (nettyConfig.getWsAdditionalPipelineInitializer() != null) - nettyConfig.getWsAdditionalPipelineInitializer().initPipeline(ch.pipeline()); - } - }); - } - - private HttpContentDecompressor newHttpContentDecompressor() { - if (config.isKeepEncodingHeader()) - return new HttpContentDecompressor() { - @Override - protected String getTargetContentEncoding(String contentEncoding) throws Exception { - return contentEncoding; - } - }; - else - return new HttpContentDecompressor(); - } - - public final void tryToOfferChannelToPool(Channel channel, AsyncHandler handler, boolean keepAlive, Object partitionKey) { - if (channel.isActive() && keepAlive && channel.isActive()) { - LOGGER.debug("Adding key: {} for channel {}", partitionKey, channel); - Channels.setDiscard(channel); - if (handler instanceof AsyncHandlerExtensions) { - AsyncHandlerExtensions.class.cast(handler).onConnectionOffer(channel); - } - channelPool.offer(channel, partitionKey); - if (maxConnectionsPerHostEnabled) - channelId2PartitionKey.putIfAbsent(channel, partitionKey); - } else { - // not offered - closeChannel(channel); - } - } - - public Channel poll(Uri uri, String virtualHost, ProxyServer proxy, ConnectionPoolPartitioning connectionPoolPartitioning) { - Object partitionKey = connectionPoolPartitioning.getPartitionKey(uri, virtualHost, proxy); - return channelPool.poll(partitionKey); - } - - public boolean removeAll(Channel connection) { - return channelPool.removeAll(connection); - } - - private boolean tryAcquireGlobal() { - return !maxTotalConnectionsEnabled || freeChannels.tryAcquire(); - } - - private Semaphore getFreeConnectionsForHost(Object partitionKey) { - return freeChannelsPerHost.computeIfAbsent(partitionKey, semaphoreComputer); - } - - private boolean tryAcquirePerHost(Object partitionKey) { - return !maxConnectionsPerHostEnabled || getFreeConnectionsForHost(partitionKey).tryAcquire(); - } - - public void preemptChannel(Object partitionKey) throws IOException { - if (!channelPool.isOpen()) - throw poolAlreadyClosed; - if (!tryAcquireGlobal()) - throw tooManyConnections; - if (!tryAcquirePerHost(partitionKey)) { - if (maxTotalConnectionsEnabled) - freeChannels.release(); - - throw tooManyConnectionsPerHost; - } - } - - 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); - removeAll(channel); - Channels.setDiscard(channel); - Channels.silentlyCloseChannel(channel); - openChannels.remove(channel); - } - - public void abortChannelPreemption(Object partitionKey) { - if (maxTotalConnectionsEnabled) - freeChannels.release(); - if (maxConnectionsPerHostEnabled) - getFreeConnectionsForHost(partitionKey).release(); - } - - public void registerOpenChannel(Channel channel, Object partitionKey) { - openChannels.add(channel); - if (maxConnectionsPerHostEnabled) { - channelId2PartitionKey.put(channel, partitionKey); - } - } - - private HttpClientCodec newHttpClientCodec() { - return new HttpClientCodec(// - config.getHttpClientCodecMaxInitialLineLength(),// - config.getHttpClientCodecMaxHeaderSize(),// - config.getHttpClientCodecMaxChunkSize(),// - false); - } - - private SslHandler createSslHandler(String peerHost, int peerPort) throws GeneralSecurityException { - SSLEngine sslEngine = sslEngineFactory.newSSLEngine(peerHost, peerPort); - SslHandler sslHandler = new SslHandler(sslEngine); - if (handshakeTimeout > 0) - sslHandler.setHandshakeTimeoutMillis(handshakeTimeout); - return sslHandler; - } - - public static boolean isSslHandlerConfigured(ChannelPipeline pipeline) { - return pipeline.get(SSL_HANDLER) != null; - } - - public void upgradeProtocol(ChannelPipeline pipeline, String scheme, String host, int port) throws 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 SslHandler addSslHandler(ChannelPipeline pipeline, Uri uri, String virtualHost) throws GeneralSecurityException { - String peerHost; - int peerPort; - - if (virtualHost != null) { - int i = virtualHost.indexOf(':'); - if (i == -1) { - peerHost = virtualHost; - peerPort = getSchemeDefaultPort(uri.getScheme()); - } else { - peerHost = virtualHost.substring(0, i); - peerPort = Integer.valueOf(virtualHost.substring(i + 1)); - } - - } else { - peerHost = uri.getHost(); - peerPort = getExplicitPort(uri); - } - - SslHandler sslHandler = createSslHandler(peerHost, peerPort); - pipeline.addFirst(ChannelManager.SSL_HANDLER, sslHandler); - return sslHandler; - } - - /** - * 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, Uri uri, String virtualHost) throws GeneralSecurityException { - - boolean sslHandlerConfigured = isSslHandlerConfigured(pipeline); - - if (isSecure(uri)) { - if (!sslHandlerConfigured) - addSslHandler(pipeline, uri, virtualHost); - - } else if (sslHandlerConfigured) - pipeline.remove(SSL_HANDLER); - } - - public Bootstrap getBootstrap(Uri uri, boolean useProxy) { - return uri.getScheme().startsWith(WS) && !useProxy ? wsBootstrap : httpBootstrap; - } - - 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, config.getWebSocketMaxFrameSize())); - pipeline.addAfter(WS_DECODER_HANDLER, WS_FRAME_AGGREGATOR, new WebSocketFrameAggregator(config.getWebSocketMaxBufferSize())); - } - - public final Callback newDrainCallback(final NettyResponseFuture future, final Channel channel, final boolean keepAlive, final Object partitionKey) { - - return new Callback(future) { - public void call() { - tryToOfferChannelToPool(channel, future.getAsyncHandler(), keepAlive, partitionKey); - } - }; - } - - public void drainChannelAndOffer(final Channel channel, final NettyResponseFuture future) { - drainChannelAndOffer(channel, future, future.isKeepAlive(), future.getPartitionKey()); - } - - public void drainChannelAndOffer(final Channel channel, final NettyResponseFuture future, boolean keepAlive, Object partitionKey) { - Channels.setAttribute(channel, newDrainCallback(future, channel, keepAlive, partitionKey)); - } - - public void flushPartition(String partitionId) { - channelPool.flushPartition(partitionId); - } - - public void flushPartitions(ChannelPoolPartitionSelector selector) { - channelPool.flushPartitions(selector); - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/Channels.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/Channels.java deleted file mode 100755 index 67adaa08f0..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/Channels.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.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 class Channels { - - private static final Logger LOGGER = LoggerFactory.getLogger(Channels.class); - - 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.isActive(); - } - - public static void silentlyCloseChannel(Channel channel) { - try { - if (channel != null && channel.isActive()) - channel.close(); - } catch (Throwable t) { - LOGGER.debug("Failed to close channel", t); - } - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/CleanupChannelGroup.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/CleanupChannelGroup.java deleted file mode 100755 index b8f009bcdd..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/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.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. - Channels.silentlyCloseChannel(channel); - return false; - } - - return super.add(channel); - } finally { - this.lock.readLock().unlock(); - } - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java deleted file mode 100755 index f7e23ee262..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/NettyConnectListener.java +++ /dev/null @@ -1,149 +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.netty.channel; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getBaseUrl; -import static org.asynchttpclient.util.HttpUtils.isSecure; -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 org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.Request; -import org.asynchttpclient.handler.AsyncHandlerExtensions; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.future.StackTraceInspector; -import org.asynchttpclient.netty.request.NettyRequestSender; -import org.asynchttpclient.uri.Uri; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Non Blocking connect. - */ -public final class NettyConnectListener implements ChannelFutureListener { - - private final static Logger LOGGER = LoggerFactory.getLogger(NettyConnectListener.class); - - private final NettyRequestSender requestSender; - private final NettyResponseFuture future; - private final ChannelManager channelManager; - private final boolean channelPreempted; - private final Object partitionKey; - - public NettyConnectListener(NettyResponseFuture future,// - NettyRequestSender requestSender,// - ChannelManager channelManager,// - boolean channelPreempted,// - Object partitionKey) { - this.future = future; - this.requestSender = requestSender; - this.channelManager = channelManager; - this.channelPreempted = channelPreempted; - this.partitionKey = partitionKey; - } - - private void abortChannelPreemption() { - if (channelPreempted) - channelManager.abortChannelPreemption(partitionKey); - } - - private void writeRequest(Channel channel) { - - LOGGER.debug("Using non-cached Channel {} for {} '{}'", - channel, - future.getNettyRequest().getHttpRequest().getMethod(), - future.getNettyRequest().getHttpRequest().getUri()); - - Channels.setAttribute(channel, future); - - if (future.isDone()) { - abortChannelPreemption(); - return; - } - - if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onConnectionOpened(channel); - - channelManager.registerOpenChannel(channel, partitionKey); - future.attachChannel(channel, false); - requestSender.writeRequest(future, channel); - } - - private void onFutureSuccess(final Channel channel) throws Exception { - - Request request = future.getRequest(); - Uri uri = request.getUri(); - - // in case of proxy tunneling, we'll add the SslHandler later, after the CONNECT request - if (future.getProxyServer() == null && isSecure(uri)) { - SslHandler sslHandler = channelManager.addSslHandler(channel.pipeline(), uri, request.getVirtualHost()); - sslHandler.handshakeFuture().addListener(new GenericFutureListener>() { - @Override - public void operationComplete(Future handshakeFuture) throws Exception { - - if (handshakeFuture.isSuccess()) { - final AsyncHandler asyncHandler = future.getAsyncHandler(); - if (asyncHandler instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(asyncHandler).onSslHandshakeCompleted(); - - writeRequest(channel); - } else { - onFutureFailure(channel, handshakeFuture.cause()); - } - } - }); - - } else { - writeRequest(channel); - } - } - - private void onFutureFailure(Channel channel, Throwable cause) { - - abortChannelPreemption(); - - boolean canRetry = future.canRetry(); - LOGGER.debug("Trying to recover from failing to connect channel {} with a retry value of {} ", channel, canRetry); - if (canRetry// - && cause != null// - && (future.getState() != NettyResponseFuture.STATE.NEW || StackTraceInspector.recoverOnNettyDisconnectException(cause))) { - - if (requestSender.retry(future)) { - return; - } - } - - LOGGER.debug("Failed to recover from connect exception: {} with channel {}", cause, channel); - - boolean printCause = cause != null && cause.getMessage() != null; - String printedCause = printCause ? cause.getMessage() : getBaseUrl(future.getUri()); - 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/netty4/src/main/java/org/asynchttpclient/netty/channel/pool/ChannelPool.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/pool/ChannelPool.java deleted file mode 100755 index 5e29ef24b2..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/pool/ChannelPool.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.netty.channel.pool; - -import org.asynchttpclient.netty.channel.pool.ChannelPoolPartitionSelector; - -import io.netty.channel.Channel; - -public interface ChannelPool { - - /** - * Add a channel to the pool - * - * @param partitionKey a key used to retrieve the cached channel - * @param channel an I/O 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 - */ - 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. 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(); - - /** - * Flush a partition - * - * @param partitionKey - */ - void flushPartition(Object partitionKey); - - /** - * Flush partitions based on a selector - * - * @param selector - */ - void flushPartitions(ChannelPoolPartitionSelector selector); -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/pool/DefaultChannelPool.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/pool/DefaultChannelPool.java deleted file mode 100755 index 024f2e52f2..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/pool/DefaultChannelPool.java +++ /dev/null @@ -1,349 +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.netty.channel.pool; - -import static org.asynchttpclient.util.DateUtils.millisTime; -import io.netty.channel.Channel; -import io.netty.handler.ssl.SslHandler; -import io.netty.util.Timeout; -import io.netty.util.Timer; -import io.netty.util.TimerTask; -import io.netty.util.internal.chmv8.ConcurrentHashMapV8; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.Channels; -import org.asynchttpclient.netty.channel.pool.ChannelPoolPartitionSelector; -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 static final ConcurrentHashMapV8.Fun> PARTITION_COMPUTER = new ConcurrentHashMapV8.Fun>() { - @Override - public ConcurrentLinkedQueue apply(Object partitionKey) { - return new ConcurrentLinkedQueue<>(); - } - }; - - private final ConcurrentHashMapV8> partitions = new ConcurrentHashMapV8<>(); - private final ConcurrentHashMapV8 channelId2Creation = new ConcurrentHashMapV8<>(); - 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); - } - - private int channelId(Channel channel) { - return channel.hashCode(); - } - - 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 Object partitionKey; - - ChannelCreation(long creationTime, Object partitionKey) { - this.creationTime = creationTime; - this.partitionKey = partitionKey; - } - } - - 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 = channelId2Creation.get(channelId(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 partition, long now) { - // lazy create - List idleTimeoutChannels = null; - for (IdleChannel idleChannel : partition) { - 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 false; - } - } - 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 (isChannelCloseable(idleChannel.channel)) { - 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; - } - - public void run(Timeout timeout) throws Exception { - - if (isClosed.get()) - return; - - try { - if (LOGGER.isDebugEnabled()) - for (Object key: partitions.keySet()) { - LOGGER.debug("Entry count for : {} : {}", key, partitions.get(key).size()); - } - - long start = millisTime(); - int closedCount = 0; - int totalCount = 0; - - for (ConcurrentLinkedQueue partition : partitions.values()) { - - // store in intermediate unsynchronized lists to minimize - // the impact on the ConcurrentLinkedQueue - if (LOGGER.isDebugEnabled()) - totalCount += partition.size(); - - List closedChannels = closeChannels(expiredChannels(partition, start)); - - if (!closedChannels.isEmpty()) { - for (IdleChannel closedChannel : closedChannels) - channelId2Creation.remove(channelId(closedChannel.channel)); - - partition.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()); - } - } - - /** - * {@inheritDoc} - */ - public boolean offer(Channel channel, Object partitionKey) { - if (isClosed.get() || (!sslConnectionPoolEnabled && channel.pipeline().get(SslHandler.class) != null)) - return false; - - long now = millisTime(); - - if (isTTLExpired(channel, now)) - return false; - - boolean added = partitions.computeIfAbsent(partitionKey, PARTITION_COMPUTER).add(new IdleChannel(channel, now)); - if (added) - channelId2Creation.putIfAbsent(channelId(channel), new ChannelCreation(now, partitionKey)); - - return added; - } - - /** - * {@inheritDoc} - */ - public Channel poll(Object partitionKey) { - - IdleChannel idleChannel = null; - ConcurrentLinkedQueue partition = partitions.get(partitionKey); - if (partition != null) { - while (idleChannel == null) { - idleChannel = partition.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 = channelId2Creation.remove(channelId(channel)); - return !isClosed.get() && creation != null && partitions.get(creation.partitionKey).remove(channel); - } - - /** - * {@inheritDoc} - */ - public boolean isOpen() { - return !isClosed.get(); - } - - /** - * {@inheritDoc} - */ - public void destroy() { - if (isClosed.getAndSet(true)) - return; - - for (ConcurrentLinkedQueue partition : partitions.values()) { - for (IdleChannel idleChannel : partition) - close(idleChannel.channel); - } - - partitions.clear(); - channelId2Creation.clear(); - } - - private void close(Channel channel) { - // FIXME pity to have to do this here - Channels.setDiscard(channel); - channelId2Creation.remove(channelId(channel)); - Channels.silentlyCloseChannel(channel); - } - - private void flushPartition(Object partitionKey, ConcurrentLinkedQueue partition) { - if (partition != null) { - partitions.remove(partitionKey); - for (IdleChannel idleChannel : partition) - close(idleChannel.channel); - } - } - - @Override - public void flushPartition(Object partitionKey) { - flushPartition(partitionKey, partitions.get(partitionKey)); - } - - @Override - public void flushPartitions(ChannelPoolPartitionSelector selector) { - - for (Map.Entry> partitionsEntry : partitions.entrySet()) { - Object partitionKey = partitionsEntry.getKey(); - if (selector.select(partitionKey)) - flushPartition(partitionKey, partitionsEntry.getValue()); - } - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/pool/NoopChannelPool.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/pool/NoopChannelPool.java deleted file mode 100755 index 44c37804d7..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/channel/pool/NoopChannelPool.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.netty.channel.pool; - -import org.asynchttpclient.netty.channel.pool.ChannelPoolPartitionSelector; - -import io.netty.channel.Channel; - -public class NoopChannelPool implements ChannelPool { - - @Override - public boolean offer(Channel channel, Object partitionKey) { - return false; - } - - @Override - public Channel poll(Object partitionKey) { - return null; - } - - @Override - public boolean removeAll(Channel channel) { - return false; - } - - @Override - public boolean isOpen() { - return true; - } - - @Override - public void destroy() { - } - - @Override - public void flushPartition(Object partitionKey) { - } - - @Override - public void flushPartitions(ChannelPoolPartitionSelector selector) { - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/handler/DefaultConnectionStrategy.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/handler/DefaultConnectionStrategy.java deleted file mode 100644 index 639fcd0a2a..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/handler/DefaultConnectionStrategy.java +++ /dev/null @@ -1,59 +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.netty.handler; - -import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION; -import static io.netty.handler.codec.http.HttpHeaders.Values.CLOSE; -import static io.netty.handler.codec.http.HttpHeaders.Values.KEEP_ALIVE; -import io.netty.handler.codec.http.HttpMessage; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.HttpVersion; - -import org.asynchttpclient.channel.pool.ConnectionStrategy; - -/** - * Connection strategy implementing standard HTTP 1.0/1.1 behaviour. - */ -public class DefaultConnectionStrategy implements ConnectionStrategy { - - /** - * Implemented in accordance with RFC 7230 section 6.1 - * https://tools.ietf.org/html/rfc7230#section-6.1 - */ - @Override - public boolean keepAlive(HttpRequest request, HttpResponse response) { - - String responseConnectionHeader = connectionHeader(response); - - if (CLOSE.equalsIgnoreCase(responseConnectionHeader)) { - return false; - } else { - String requestConnectionHeader = connectionHeader(request); - - if (request.getProtocolVersion() == HttpVersion.HTTP_1_0) { - // only use keep-alive if both parties agreed upon it - return KEEP_ALIVE.equalsIgnoreCase(requestConnectionHeader) && KEEP_ALIVE.equalsIgnoreCase(responseConnectionHeader); - - } else { - // 1.1+, keep-alive is default behavior - return !CLOSE.equalsIgnoreCase(requestConnectionHeader); - } - } - } - - private String connectionHeader(HttpMessage message) { - return message.headers().get(CONNECTION); - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/handler/HttpProtocol.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/handler/HttpProtocol.java deleted file mode 100755 index e3c87489d4..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/handler/HttpProtocol.java +++ /dev/null @@ -1,510 +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.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.ntlm.NtlmUtils.getNTLM; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getExplicitPort; -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.security.GeneralSecurityException; -import java.util.List; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHandler.State; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Realm.AuthScheme; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.channel.pool.ConnectionStrategy; -import org.asynchttpclient.netty.Callback; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.netty.NettyResponseBodyPart; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.NettyResponseHeaders; -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.ntlm.NtlmEngine; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.spnego.SpnegoEngine; -import org.asynchttpclient.spnego.SpnegoEngineException; -import org.asynchttpclient.uri.Uri; - -public final class HttpProtocol extends Protocol { - - private final ConnectionStrategy connectionStrategy; - - public HttpProtocol(ChannelManager channelManager, AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, NettyRequestSender requestSender) { - super(channelManager, config, nettyConfig, requestSender); - - connectionStrategy = nettyConfig.getConnectionStrategy(); - } - - private Realm kerberosChallenge(Channel channel,// - List authHeaders,// - Request request,// - FluentCaseInsensitiveStringsMap headers,// - Realm realm,// - NettyResponseFuture future) { - - Uri uri = request.getUri(); - String host = request.getVirtualHost() == null ? uri.getHost() : request.getVirtualHost(); - try { - String challengeHeader = SpnegoEngine.instance().generateToken(host); - headers.remove(HttpHeaders.Names.AUTHORIZATION); - headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); - - return new Realm.RealmBuilder().clone(realm)// - .setUri(uri)// - .setMethodName(request.getMethod())// - .setScheme(Realm.AuthScheme.KERBEROS)// - .build(); - - - } catch (SpnegoEngineException throwable) { - String ntlmAuthenticate = getNTLM(authHeaders); - if (ntlmAuthenticate != null) { - return ntlmChallenge(ntlmAuthenticate, request, headers, realm, future); - } - requestSender.abort(channel, future, throwable); - return null; - } - } - - private Realm kerberosProxyChallenge(Channel channel,// - List proxyAuth,// - Request request,// - ProxyServer proxyServer,// - FluentCaseInsensitiveStringsMap headers,// - NettyResponseFuture future) { - - try { - String challengeHeader = SpnegoEngine.instance().generateToken(proxyServer.getHost()); - headers.remove(HttpHeaders.Names.AUTHORIZATION); - headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); - - return proxyServer.realmBuilder()// - .setUri(request.getUri())// - .setMethodName(request.getMethod())// - .setScheme(Realm.AuthScheme.KERBEROS)// - .build(); - - } catch (SpnegoEngineException throwable) { - String ntlmAuthenticate = getNTLM(proxyAuth); - if (ntlmAuthenticate != null) { - return ntlmProxyChallenge(ntlmAuthenticate, request, proxyServer, headers, future); - } - 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(String authenticateHeader,// - Request request,// - FluentCaseInsensitiveStringsMap headers,// - Realm realm,// - NettyResponseFuture future) { - - if (authenticateHeader.equals("NTLM")) { - // server replied bare NTLM => we didn't preemptively sent Type1Msg - String challengeHeader = NtlmEngine.INSTANCE.generateType1Msg(); - - addNTLMAuthorizationHeader(headers, challengeHeader, false); - future.getAndSetAuth(false); - - } else { - addType3NTLMAuthorizationHeader(authenticateHeader, headers, realm, false); - } - - return new Realm.RealmBuilder().clone(realm)// - .setUri(request.getUri())// - .setMethodName(request.getMethod())// - .build(); - } - - private Realm ntlmProxyChallenge(String authenticateHeader,// - Request request,// - ProxyServer proxyServer,// - FluentCaseInsensitiveStringsMap headers,// - NettyResponseFuture future) { - - future.getAndSetAuth(false); - headers.remove(HttpHeaders.Names.PROXY_AUTHORIZATION); - - Realm realm = proxyServer.realmBuilder()// - .setScheme(AuthScheme.NTLM)// - .setUri(request.getUri())// - .setMethodName(request.getMethod()).build(); - - addType3NTLMAuthorizationHeader(authenticateHeader, headers, realm, true); - - return realm; - } - - private void addType3NTLMAuthorizationHeader(String authenticateHeader, FluentCaseInsensitiveStringsMap headers, Realm realm, - boolean proxyInd) { - headers.remove(authorizationHeaderName(proxyInd)); - - if (authenticateHeader.startsWith("NTLM ")) { - String serverChallenge = authenticateHeader.substring("NTLM ".length()).trim(); - String challengeHeader = NtlmEngine.INSTANCE.generateType3Msg(realm.getPrincipal(), realm.getPassword(), realm.getNtlmDomain(), realm.getNtlmHost(), serverChallenge); - addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd); - } - } - - private void finishUpdate(final NettyResponseFuture future, Channel channel, boolean expectOtherChunks) throws IOException { - - future.cancelTimeouts(); - - boolean keepAlive = future.isKeepAlive(); - if (expectOtherChunks && keepAlive) - channelManager.drainChannelAndOffer(channel, future); - else - channelManager.tryToOfferChannelToPool(channel, future.getAsyncHandler(), keepAlive, future.getPartitionKey()); - - try { - future.done(); - } catch (Exception t) { - // Never propagate exception once we know we are done. - logger.debug(t.getMessage(), t); - } - } - - 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 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"); - String ntlmAuthenticate = getNTLM(wwwAuthHeaders); - if (!wwwAuthHeaders.contains("Kerberos") && ntlmAuthenticate != null) { - // NTLM - newRealm = ntlmChallenge(ntlmAuthenticate, request, request.getHeaders(), realm, future); - - } else if (negociate) { - newRealm = kerberosChallenge(channel, wwwAuthHeaders, request, request.getHeaders(), realm, future); - // SPNEGO KERBEROS - if (newRealm == null) - return true; - - } else { - // BASIC or DIGEST - newRealm = new Realm.RealmBuilder()// - .clone(realm)// - .setUri(request.getUri())// - .setMethodName(request.getMethod())// - .setUsePreemptiveAuth(true)// - .parseWWWAuthenticateHeader(wwwAuthHeaders.get(0))// - .build(); - } - - final Request nextRequest = new RequestBuilder(future.getRequest()).setHeaders(request.getHeaders()).setRealm(newRealm).build(); - - logger.debug("Sending authentication to {}", request.getUri()); - if (future.isKeepAlive() && !HttpHeaders.isTransferEncodingChunked(response)) { - future.setReuseChannel(true); - requestSender.drainChannelAndExecuteNextRequest(channel, future, nextRequest); - } else { - channelManager.closeChannel(channel); - requestSender.sendNextRequest(nextRequest, future); - } - - 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); - // directly send the body - Channels.setAttribute(channel, new Callback(future) { - @Override - public void call() throws IOException { - Channels.setAttribute(channel, future); - 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"); - String ntlmAuthenticate = getNTLM(proxyAuthHeaders); - if (!proxyAuthHeaders.contains("Kerberos") && ntlmAuthenticate != null) { - // NTLM - newRealm = ntlmProxyChallenge(ntlmAuthenticate, request, proxyServer, requestHeaders, future); - - } else if (negociate) { - // SPNEGO KERBEROS - newRealm = kerberosProxyChallenge(channel, proxyAuthHeaders, request, proxyServer, requestHeaders, future); - if (newRealm == null) - return true; - - } else { - // BASIC or DIGEST - newRealm = new Realm.RealmBuilder().clone(realm)// - .setUri(request.getUri())// - .setOmitQuery(true)// - .setMethodName(request.getMethod())// - .setUsePreemptiveAuth(true)// - .parseProxyAuthenticateHeader(proxyAuthHeaders.get(0))// - .build(); - } - - final Request nextRequest = new RequestBuilder(future.getRequest()).setHeaders(request.getHeaders()).setRealm(newRealm).build(); - - logger.debug("Sending proxy authentication to {}", request.getUri()); - if (future.isKeepAlive() && !HttpHeaders.isTransferEncodingChunked(response)) { - future.setConnectAllowed(true); - future.setReuseChannel(true); - requestSender.drainChannelAndExecuteNextRequest(channel, future, nextRequest); - } else { - channelManager.closeChannel(channel); - 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); - - Uri requestUri = request.getUri(); - String scheme = requestUri.getScheme(); - String host = requestUri.getHost(); - int port = getExplicitPort(requestUri); - - logger.debug("Connecting to proxy {} for scheme {}", proxyServer, scheme); - - try { - channelManager.upgradeProtocol(channel.pipeline(), scheme, host, port); - future.setReuseChannel(true); - future.setConnectAllowed(false); - requestSender.drainChannelAndExecuteNextRequest(channel, future, new RequestBuilder(future.getRequest()).build()); - - } catch (GeneralSecurityException ex) { - requestSender.abort(channel, future, ex); - } - - 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(connectionStrategy.keepAlive(httpRequest, response)); - - NettyResponseStatus status = new NettyResponseStatus(future.getUri(), config, response, channel); - 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, realm) || // - exitAfterHandlingConnect(channel, future, request, proxyServer, statusCode, httpRequest) || // - exitAfterHandlingStatus(channel, future, response, handler, status) || // - exitAfterHandlingHeaders(channel, future, response, handler, responseHeaders); - } - - private void handleChunk(HttpContent chunk,// - final Channel channel,// - final NettyResponseFuture future,// - AsyncHandler handler) throws IOException, Exception { - - 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 { - buf.release(); - } - - if (interrupt || last) - finishUpdate(future, channel, !last); - } - - @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) { - if (handleHttpResponse((HttpResponse) e, channel, future, handler)) - return; - - } 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.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/netty4/src/main/java/org/asynchttpclient/netty/handler/Processor.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/handler/Processor.java deleted file mode 100755 index 869893dd27..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/handler/Processor.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.netty.handler; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.CHANNEL_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.HttpContent; -import io.netty.handler.codec.http.LastHttpContent; - -import java.io.IOException; -import java.nio.channels.ClosedChannelException; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.Callback; -import org.asynchttpclient.netty.DiscardEvent; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.ChannelManager; -import org.asynchttpclient.netty.channel.Channels; -import org.asynchttpclient.netty.future.StackTraceInspector; -import org.asynchttpclient.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); - - 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) { - Callback ac = (Callback) attribute; - if (msg instanceof LastHttpContent) { - ac.call(); - } else if (!(msg instanceof HttpContent)) { - LOGGER.info("Received unexpected message while expecting a chunk: " + msg); - ac.call(); - Channels.setDiscard(channel); - } - - } else if (attribute instanceof NettyResponseFuture) { - NettyResponseFuture future = (NettyResponseFuture) attribute; - protocol.handle(channel, future, msg); - - } else if (attribute != DiscardEvent.INSTANCE) { - // unhandled message - LOGGER.debug("Orphan channel {} with attribute {} received message {}, closing", channel, attribute, msg); - Channels.silentlyCloseChannel(channel); - } - } - - 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); - requestSender.handleUnexpectedClosedChannel(channel, future); - } - } - - @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. - Channels.silentlyCloseChannel(channel); - return; - } - } - - if (StackTraceInspector.recoverOnReadOrWriteException(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); - Channels.silentlyCloseChannel(channel); - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/handler/Protocol.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/handler/Protocol.java deleted file mode 100755 index 6e64e68983..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/handler/Protocol.java +++ /dev/null @@ -1,225 +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.netty.handler; - -import static io.netty.handler.codec.http.HttpHeaders.Names.AUTHORIZATION; -import static io.netty.handler.codec.http.HttpHeaders.Names.PROXY_AUTHORIZATION; -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.util.AsyncHttpProviderUtils.followRedirect; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.isSameBase; -import io.netty.channel.Channel; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpResponse; - -import java.util.HashSet; -import java.util.Set; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Realm.AuthScheme; -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.FilterException; -import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.handler.MaxRedirectException; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig; -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; - -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 MaxRedirectException maxRedirectException; - - 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(); - maxRedirectException = new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()); - } - - 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); - - private FluentCaseInsensitiveStringsMap propagatedHeaders(Request request, Realm realm, boolean switchToGet) { - - FluentCaseInsensitiveStringsMap headers = request.getHeaders()// - .delete(HttpHeaders.Names.HOST)// - .delete(HttpHeaders.Names.CONTENT_LENGTH)// - .delete(HttpHeaders.Names.CONTENT_TYPE); - - if (realm != null && realm.getScheme() == AuthScheme.NTLM) { - headers.delete(AUTHORIZATION)// - .delete(PROXY_AUTHORIZATION); - } - return headers; - } - - protected boolean exitAfterHandlingRedirect(// - Channel channel,// - NettyResponseFuture future,// - HttpResponse response,// - Request request,// - int statusCode,// - Realm realm) throws Exception { - - if (followRedirect(config, request) && REDIRECT_STATUSES.contains(statusCode)) { - if (future.incrementAndGetCurrentRedirectCount() >= config.getMaxRedirects()) { - throw maxRedirectException; - - } else { - // We must allow 401 handling again. - future.getAndSetAuth(false); - - String originalMethod = request.getMethod(); - boolean switchToGet = !originalMethod.equals("GET") && (statusCode == 303 || (statusCode == 302 && !config.isStrict302Handling())); - - final RequestBuilder requestBuilder = new RequestBuilder(switchToGet ? "GET" : originalMethod)// - .setCookies(request.getCookies())// - .setConnectionPoolPartitioning(request.getConnectionPoolPartitioning())// - .setFollowRedirect(true)// - .setLocalInetAddress(request.getLocalAddress())// - .setNameResolver(request.getNameResolver())// - .setProxyServer(request.getProxyServer())// - .setRealm(request.getRealm())// - .setRequestTimeout(request.getRequestTimeout()); - - requestBuilder.setHeaders(propagatedHeaders(request, realm, switchToGet)); - - // 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(HttpHeaders.Names.LOCATION); - Uri newUri = Uri.create(future.getUri(), location); - - logger.debug("Redirecting to {}", newUri); - - for (String cookieStr : responseHeaders.getAll(HttpHeaders.Names.SET_COOKIE)) { - Cookie c = CookieDecoder.decode(cookieStr); - if (c != null) - requestBuilder.addOrReplaceCookie(c); - } - - requestBuilder.setHeaders(propagatedHeaders(future.getRequest(), realm, switchToGet)); - - boolean sameBase = isSameBase(request.getUri(), 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(); - - logger.debug("Sending redirect to {}", newUri); - - if (future.isKeepAlive() && !HttpHeaders.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; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - protected boolean exitAfterProcessingFilters(// - Channel channel,// - NettyResponseFuture future,// - AsyncHandler handler, // - HttpResponseStatus status,// - HttpResponseHeaders responseHeaders) { - - 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/netty4/src/main/java/org/asynchttpclient/netty/handler/WebSocketProtocol.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/handler/WebSocketProtocol.java deleted file mode 100755 index d1ecb588ae..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/handler/WebSocketProtocol.java +++ /dev/null @@ -1,219 +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.netty.handler; - -import static io.netty.handler.codec.http.HttpResponseStatus.SWITCHING_PROTOCOLS; -import static org.asynchttpclient.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.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.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.AsyncHandler.State; -import org.asynchttpclient.netty.Callback; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.netty.NettyResponseBodyPart; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.NettyResponseHeaders; -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; - -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, config)); - } catch (Exception ex) { - logger.warn("onSuccess unexpected exception", ex); - } - } - } - - private class UpgradeCallback extends Callback { - - private final Channel channel; - private final HttpResponse response; - - public UpgradeCallback(NettyResponseFuture future, Channel channel, HttpResponse response) { - super(future); - this.channel = channel; - this.response = response; - } - - @Override - public void call() throws Exception { - - WebSocketUpgradeHandler handler = WebSocketUpgradeHandler.class.cast(future.getAsyncHandler()); - Request request = future.getRequest(); - - HttpResponseStatus status = new NettyResponseStatus(future.getUri(), config, response, channel); - HttpResponseHeaders responseHeaders = new NettyResponseHeaders(response.headers()); - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - - if (exitAfterProcessingFilters(channel, future, handler, status, responseHeaders)) { - return; - } - - future.setHttpHeaders(response.headers()); - if (exitAfterHandlingRedirect(channel, future, response, request, response.getStatus().code(), realm)) - 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); - 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(); - // set back the future so the protocol gets notified of frames - Channels.setAttribute(channel, future); - } - - } - - @Override - public void handle(Channel channel, NettyResponseFuture future, Object e) throws Exception { - - if (e instanceof HttpResponse) { - HttpResponse response = (HttpResponse) e; - Channels.setAttribute(channel, new UpgradeCallback(future, channel, response)); - - } else if (e instanceof WebSocketFrame) { - - final WebSocketFrame frame = (WebSocketFrame) e; - WebSocketUpgradeHandler handler = WebSocketUpgradeHandler.class.cast(future.getAsyncHandler()); - 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) future.getAsyncHandler(); - - 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) future.getAsyncHandler(); - 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/netty4/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/NettyRequest.java deleted file mode 100755 index 65c067b4cb..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/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.netty.request; - -import org.asynchttpclient.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/netty4/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java deleted file mode 100755 index 096a2fdc47..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/NettyRequestFactory.java +++ /dev/null @@ -1,232 +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.netty.request; - -import static io.netty.handler.codec.http.HttpHeaders.Names.ACCEPT; -import static io.netty.handler.codec.http.HttpHeaders.Names.ACCEPT_ENCODING; -import static io.netty.handler.codec.http.HttpHeaders.Names.AUTHORIZATION; -import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION; -import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH; -import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; -import static io.netty.handler.codec.http.HttpHeaders.Names.COOKIE; -import static io.netty.handler.codec.http.HttpHeaders.Names.HOST; -import static io.netty.handler.codec.http.HttpHeaders.Names.ORIGIN; -import static io.netty.handler.codec.http.HttpHeaders.Names.PROXY_AUTHORIZATION; -import static io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY; -import static io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_VERSION; -import static io.netty.handler.codec.http.HttpHeaders.Names.TRANSFER_ENCODING; -import static io.netty.handler.codec.http.HttpHeaders.Names.UPGRADE; -import static io.netty.handler.codec.http.HttpHeaders.Names.USER_AGENT; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.DEFAULT_CHARSET; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.hostHeader; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.urlEncodeFormParams; -import static org.asynchttpclient.util.HttpUtils.isSecure; -import static org.asynchttpclient.util.HttpUtils.isWebSocket; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; -import static org.asynchttpclient.ws.WebSocketUtils.getKey; -import io.netty.buffer.ByteBuf; -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.nio.charset.Charset; -import java.util.List; -import java.util.Map.Entry; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.cookie.CookieEncoder; -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.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.HttpUtils; -import org.asynchttpclient.util.StringUtils; - -public final class NettyRequestFactory extends NettyRequestFactoryBase { - - public static final String GZIP_DEFLATE = HttpHeaders.Values.GZIP + "," + HttpHeaders.Values.DEFLATE; - - public NettyRequestFactory(AsyncHttpClientConfig config) { - super(config); - } - - protected List getProxyAuthorizationHeader(Request request) { - return request.getHeaders().get(PROXY_AUTHORIZATION); - } - - private NettyBody body(Request request, boolean connect) { - NettyBody nettyBody = null; - if (!connect) { - - Charset bodyCharset = request.getBodyCharset() == null ? DEFAULT_CHARSET : request.getBodyCharset(); - - 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.getStreamData() != null) - nettyBody = new NettyInputStreamBody(request.getStreamData(), config); - - else if (isNonEmpty(request.getFormParams())) { - - String contentType = null; - if (!request.getHeaders().containsKey(CONTENT_TYPE)) - contentType = HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED; - - nettyBody = new NettyByteBufferBody(urlEncodeFormParams(request.getFormParams(), bodyCharset), contentType); - - } else if (isNonEmpty(request.getParts())) - nettyBody = new NettyMultipartBody(request.getParts(), 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) - nettyBody = new NettyInputStreamBody(InputStreamBodyGenerator.class.cast(request.getBodyGenerator()).getInputStream(), config); - - 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 forceConnect, ProxyServer proxyServer) { - - Uri uri = request.getUri(); - HttpMethod method = forceConnect ? HttpMethod.CONNECT : HttpMethod.valueOf(request.getMethod()); - boolean connect = method == HttpMethod.CONNECT; - - boolean allowConnectionPooling = config.isAllowPoolingConnections() && (!HttpUtils.isSecure(uri) || config.isAllowPoolingSslConnections()); - - HttpVersion httpVersion = !allowConnectionPooling || (connect && proxyServer.isForceHttp10()) ? HttpVersion.HTTP_1_0 : HttpVersion.HTTP_1_1; - String requestUri = requestUri(uri, proxyServer, connect); - - NettyBody body = body(request, connect); - - HttpRequest httpRequest; - NettyRequest nettyRequest; - if (body instanceof NettyDirectBody) { - ByteBuf buf = NettyDirectBody.class.cast(body).byteBuf(); - 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 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 (!connect) { - // assign headers as configured on request - for (Entry> header : request.getHeaders()) { - headers.set(header.getKey(), header.getValue()); - } - - if (isNonEmpty(request.getCookies())) - headers.set(COOKIE, CookieEncoder.encode(request.getCookies())); - - if (config.isCompressionEnforced() && !headers.contains(ACCEPT_ENCODING)) - headers.set(ACCEPT_ENCODING, GZIP_DEFLATE); - } - - if (body != null) { - if (body.getContentLength() < 0) - headers.set(TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); - else - headers.set(CONTENT_LENGTH, body.getContentLength()); - - if (body.getContentType() != null) - headers.set(CONTENT_TYPE, body.getContentType()); - } - - // connection header and friends - boolean webSocket = isWebSocket(uri.getScheme()); - if (!connect && webSocket) { - headers.set(UPGRADE, HttpHeaders.Values.WEBSOCKET)// - .set(CONNECTION, HttpHeaders.Values.UPGRADE)// - .set(ORIGIN, "http://" + uri.getHost() + ":" + (uri.getPort() == -1 ? isSecure(uri.getScheme()) ? 443 : 80 : uri.getPort()))// - .set(SEC_WEBSOCKET_KEY, getKey())// - .set(SEC_WEBSOCKET_VERSION, "13"); - - } else if (!headers.contains(CONNECTION)) { - String connectionHeaderValue = connectionHeader(allowConnectionPooling, httpVersion == HttpVersion.HTTP_1_1); - if (connectionHeaderValue != null) - headers.set(CONNECTION, connectionHeaderValue); - } - - if (!headers.contains(HOST)) - headers.set(HOST, hostHeader(request, uri)); - - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - - // don't override authorization but append - addAuthorizationHeader(headers, systematicAuthorizationHeader(request, realm)); - - setProxyAuthorizationHeader(headers, systematicProxyAuthorizationHeader(request, proxyServer, realm, connect)); - - // Add default accept headers - if (!headers.contains(ACCEPT)) - headers.set(ACCEPT, "*/*"); - - // Add default user agent - if (!headers.contains(USER_AGENT) && config.getUserAgent() != null) - headers.set(USER_AGENT, config.getUserAgent()); - - return nettyRequest; - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java deleted file mode 100755 index e74895bbd7..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/NettyRequestSender.java +++ /dev/null @@ -1,524 +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.netty.request; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.REMOTELY_CLOSED_EXCEPTION; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getExplicitPort; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.requestTimeout; -import static org.asynchttpclient.util.HttpUtils.WS; -import static org.asynchttpclient.util.HttpUtils.useProxyConnect; -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.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.IOExceptionFilter; -import org.asynchttpclient.handler.AsyncHandlerExtensions; -import org.asynchttpclient.handler.TransferCompletionHandler; -import org.asynchttpclient.netty.Callback; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.ChannelManager; -import org.asynchttpclient.netty.channel.Channels; -import org.asynchttpclient.netty.channel.NettyConnectListener; -import org.asynchttpclient.netty.timeout.ReadTimeoutTimerTask; -import org.asynchttpclient.netty.timeout.RequestTimeoutTimerTask; -import org.asynchttpclient.netty.timeout.TimeoutsHolder; -import org.asynchttpclient.proxy.ProxyServer; -import org.asynchttpclient.uri.Uri; -import org.asynchttpclient.ws.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,// - ChannelManager channelManager,// - Timer nettyTimer,// - AtomicBoolean closed) { - this.config = config; - this.channelManager = channelManager; - this.nettyTimer = nettyTimer; - this.closed = closed; - requestFactory = new NettyRequestFactory(config); - } - - public ListenableFuture sendRequest(final Request request,// - final AsyncHandler asyncHandler,// - NettyResponseFuture future,// - boolean reclaimCache) { - - if (closed.get()) - throw new IllegalStateException("Closed"); - - validateWebSocketRequest(request, asyncHandler); - - 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(request.getUri())) - // SSL proxy, have to handle CONNECT - if (future != null && future.isConnectAllowed()) - // CONNECT forced - return sendRequestWithCertainForceConnect(request, asyncHandler, future, reclaimCache, proxyServer, true, true); - else - return sendRequestThroughSslProxy(request, asyncHandler, future, reclaimCache, proxyServer); - else - return sendRequestWithCertainForceConnect(request, asyncHandler, future, reclaimCache, 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,// - ProxyServer proxyServer,// - boolean useProxy,// - boolean forceConnect) { - - NettyResponseFuture newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, proxyServer, forceConnect); - - Channel channel = getCachedChannel(future, request, proxyServer, asyncHandler); - - if (Channels.isChannelValid(channel)) - return sendRequestWithCachedChannel(request, proxyServer, newFuture, asyncHandler, channel); - else - return sendRequestWithNewChannel(request, 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,// - ProxyServer proxyServer) { - - NettyResponseFuture newFuture = null; - for (int i = 0; i < 3; i++) { - Channel channel = getCachedChannel(future, request, proxyServer, asyncHandler); - if (Channels.isChannelValid(channel)) - if (newFuture == null) - newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, proxyServer, false); - - if (Channels.isChannelValid(channel)) - // if the channel is still active, we can use it, otherwise try gain - return sendRequestWithCachedChannel(request, proxyServer, newFuture, asyncHandler, channel); - else - // pool is empty - break; - } - - newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, proxyServer, true); - return sendRequestWithNewChannel(request, proxyServer, true, newFuture, asyncHandler, reclaimCache); - } - - private NettyResponseFuture newNettyRequestAndResponseFuture(final Request request, final AsyncHandler asyncHandler, NettyResponseFuture originalFuture, - ProxyServer proxy, boolean forceConnect) { - - NettyRequest nettyRequest = requestFactory.newNettyRequest(request, forceConnect, proxy); - - if (originalFuture == null) { - return newNettyResponseFuture(request, asyncHandler, nettyRequest, proxy); - } else { - originalFuture.setNettyRequest(nettyRequest); - originalFuture.setRequest(request); - return originalFuture; - } - } - - private Channel getCachedChannel(NettyResponseFuture future, Request request, ProxyServer proxyServer, AsyncHandler asyncHandler) { - - if (future != null && future.reuseChannel() && Channels.isChannelValid(future.channel())) - return future.channel(); - else - return pollAndVerifyCachedChannel(request, proxyServer, asyncHandler); - } - - private ListenableFuture sendRequestWithCachedChannel(Request request, ProxyServer proxy, NettyResponseFuture future, - AsyncHandler asyncHandler, Channel channel) { - - if (asyncHandler instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(asyncHandler).onConnectionPooled(channel); - - future.setState(NettyResponseFuture.STATE.POOLED); - future.attachChannel(channel, false); - - LOGGER.debug("Using cached Channel {} for {} '{}'", - channel, - future.getNettyRequest().getHttpRequest().getMethod(), - future.getNettyRequest().getHttpRequest().getUri()); - - if (Channels.isChannelValid(channel)) { - Channels.setAttribute(channel, future); - 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,// - boolean useProxy,// - NettyResponseFuture future,// - AsyncHandler asyncHandler,// - boolean reclaimCache) { - - // some headers are only set when performing the first request - HttpHeaders headers = future.getNettyRequest().getHttpRequest().headers(); - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - boolean connect = future.getNettyRequest().getHttpRequest().getMethod() == HttpMethod.CONNECT; - requestFactory.addAuthorizationHeader(headers, requestFactory.firstRequestOnlyAuthorizationHeader(request, proxy, realm)); - requestFactory.setProxyAuthorizationHeader(headers, requestFactory.firstRequestOnlyProxyAuthorizationHeader(request, proxy, connect)); - - // 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); - - boolean channelPreempted = false; - Object partitionKey = future.getPartitionKey(); - - try { - // Do not throw an exception when we need an extra connection for a - // redirect. - if (!reclaimCache) { - channelManager.preemptChannel(partitionKey); - channelPreempted = true; - } - - if (asyncHandler instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(asyncHandler).onConnectionOpen(); - - ChannelFuture channelFuture = connect(request, proxy, useProxy, bootstrap, asyncHandler); - channelFuture.addListener(new NettyConnectListener(future, this, channelManager, channelPreempted, partitionKey)); - - } catch (Throwable t) { - if (channelPreempted) - channelManager.abortChannelPreemption(partitionKey); - - abort(null, future, t.getCause() == null ? t : t.getCause()); - } - - return future; - } - - private NettyResponseFuture newNettyResponseFuture(Request request, AsyncHandler asyncHandler, NettyRequest nettyRequest, ProxyServer proxyServer) { - - NettyResponseFuture future = new NettyResponseFuture<>(// - request,// - asyncHandler,// - nettyRequest,// - config.getMaxRequestRetry(),// - request.getConnectionPoolPartitioning(),// - 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) { - - NettyRequest nettyRequest = future.getNettyRequest(); - HttpRequest httpRequest = nettyRequest.getHttpRequest(); - AsyncHandler handler = 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.isChannelValid(channel)) - return; - - try { - if (handler instanceof TransferCompletionHandler) - configureTransferAdapter(handler, httpRequest); - - if (!future.isHeadersAlreadyWrittenOnContinue()) { - if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onRequestSend(nettyRequest); - - channel.writeAndFlush(httpRequest, channel.newProgressivePromise()).addListener(new ProgressListener(config, future.getAsyncHandler(), future, true, 0L)); - } - - if (!future.isDontWriteBodyBecauseExpectContinue() && httpRequest.getMethod() != HttpMethod.CONNECT && nettyRequest.getBody() != null) - nettyRequest.getBody().write(channel, future); - - // don't bother scheduling timeouts if channel became invalid - if (Channels.isChannelValid(channel)) - scheduleTimeouts(future); - - } catch (Exception e) { - LOGGER.error("Can't write request", e); - abort(channel, future, e); - } - } - - private InetSocketAddress remoteAddress(Request request, ProxyServer proxy, boolean useProxy) throws UnknownHostException { - - InetAddress address; - Uri uri = request.getUri(); - int port = getExplicitPort(uri); - - if (request.getInetAddress() != null) { - address = request.getInetAddress(); - - } else if (!useProxy || avoidProxy(proxy, uri.getHost())) { - address = request.getNameResolver().resolve(uri.getHost()); - - } else { - address = request.getNameResolver().resolve(proxy.getHost()); - port = proxy.getPort(); - } - - return new InetSocketAddress(address, port); - } - - private ChannelFuture connect(Request request, ProxyServer proxy, boolean useProxy, Bootstrap bootstrap, AsyncHandler asyncHandler) throws UnknownHostException { - InetSocketAddress remoteAddress = remoteAddress(request, proxy, useProxy); - - if (asyncHandler instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(asyncHandler).onDnsResolved(remoteAddress.getAddress()); - - 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 readTimeoutValue = config.getReadTimeout(); - if (readTimeoutValue != -1 && readTimeoutValue < requestTimeoutInMs) { - // no need for a readTimeout that's less than the requestTimeoutInMs - Timeout readTimeout = newTimeout(new ReadTimeoutTimerTask(nettyResponseFuture, this, timeoutsHolder, requestTimeoutInMs, readTimeoutValue), readTimeoutValue); - timeoutsHolder.readTimeout = readTimeout; - } - 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()) { - future.setState(NettyResponseFuture.STATE.CLOSED); - LOGGER.debug("Aborting Future {}\n", future); - LOGGER.debug(t.getMessage(), t); - future.abort(t); - } - } - - public void handleUnexpectedClosedChannel(Channel channel, NettyResponseFuture future) { - if (future.isDone()) - channelManager.closeChannel(channel); - - else if (!retry(future)) - abort(channel, future, REMOTELY_CLOSED_EXCEPTION); - } - - public boolean retry(NettyResponseFuture future) { - - if (isClosed()) - return false; - - 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 (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().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) { - sendRequest(request, future.getAsyncHandler(), future, true); - } - - private void validateWebSocketRequest(Request request, AsyncHandler asyncHandler) { - Uri uri = request.getUri(); - boolean isWs = uri.getScheme().startsWith(WS); - if (asyncHandler instanceof WebSocketUpgradeHandler) { - if (!isWs) - throw new IllegalArgumentException("WebSocketUpgradeHandler but scheme isn't ws or wss: " + uri.getScheme()); - else if (!request.getMethod().equals(HttpMethod.GET.name())) - throw new IllegalArgumentException("WebSocketUpgradeHandler but method isn't GET: " + request.getMethod()); - } else if (isWs) { - throw new IllegalArgumentException("No WebSocketUpgradeHandler but scheme is " + uri.getScheme()); - } - } - - private Channel pollAndVerifyCachedChannel(Request request, ProxyServer proxy, AsyncHandler asyncHandler) { - - if (asyncHandler instanceof AsyncHandlerExtensions) - AsyncHandlerExtensions.class.cast(asyncHandler).onConnectionPool(); - - Uri uri = request.getUri(); - String virtualHost = request.getVirtualHost(); - final Channel channel = channelManager.poll(uri, virtualHost, proxy, request.getConnectionPoolPartitioning()); - - if (channel != null) { - LOGGER.debug("Using cached Channel {}\n for uri {}\n", channel, uri); - - try { - channelManager.verifyChannelPipeline(channel.pipeline(), uri, virtualHost); - } catch (Exception ex) { - LOGGER.debug(ex.getMessage(), ex); - } - } - return channel; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void replayRequest(final NettyResponseFuture future, FilterContext fc, Channel channel) { - - 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.drainChannelAndOffer(channel, future); - sendNextRequest(newRequest, future); - } - - public boolean isClosed() { - return closed.get(); - } - - public final Callback newExecuteNextRequestCallback(final NettyResponseFuture future, final Request nextRequest) { - - return new Callback(future) { - @Override - public void call() { - sendNextRequest(nextRequest, future); - } - }; - } - - public void drainChannelAndExecuteNextRequest(final Channel channel, final NettyResponseFuture future, Request nextRequest) { - Channels.setAttribute(channel, newExecuteNextRequestCallback(future, nextRequest)); - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/ProgressListener.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/ProgressListener.java deleted file mode 100755 index 2c025ceac6..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/ProgressListener.java +++ /dev/null @@ -1,110 +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.netty.request; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelProgressiveFuture; -import io.netty.channel.ChannelProgressiveFutureListener; - -import java.nio.channels.ClosedChannelException; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Realm; -import org.asynchttpclient.handler.ProgressAsyncHandler; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.Channels; -import org.asynchttpclient.netty.future.StackTraceInspector; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -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 || cause instanceof ClosedChannelException || StackTraceInspector.recoverOnReadOrWriteException(cause)) { - LOGGER.debug(cause.getMessage(), cause); - Channels.silentlyCloseChannel(channel); - - } 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.onHeadersWritten(); - } else { - progressAsyncHandler.onContentWritten(); - } - } - } - } - - @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/netty4/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/BodyChunkedInput.java deleted file mode 100755 index e37054b401..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/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.netty.request.body; - -import org.asynchttpclient.request.body.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/netty4/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java deleted file mode 100755 index b6934449e3..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/BodyFileRegion.java +++ /dev/null @@ -1,67 +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.netty.request.body; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; - -import org.asynchttpclient.request.body.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/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java deleted file mode 100755 index 39cbde82f0..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyBody.java +++ /dev/null @@ -1,29 +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.netty.request.body; - -import io.netty.channel.Channel; - -import java.io.IOException; - -import org.asynchttpclient.netty.NettyResponseFuture; - -public interface NettyBody { - - long getContentLength(); - - String getContentType(); - - void write(Channel channel, NettyResponseFuture future) throws IOException; -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyBodyBody.java deleted file mode 100755 index 79c6e5a669..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/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.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.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.ChannelManager; -import org.asynchttpclient.netty.request.ProgressListener; -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.FeedableBodyGenerator; -import org.asynchttpclient.request.body.generator.FeedableBodyGenerator.FeedListener; - -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 String getContentType() { - return null; - }; - - @Override - public void write(final Channel channel, NettyResponseFuture future) throws IOException { - - Object msg; - if (body instanceof RandomAccessBody && !ChannelManager.isSslHandlerConfigured(channel.pipeline()) && !config.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/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java deleted file mode 100755 index a5ab115695..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyByteArrayBody.java +++ /dev/null @@ -1,48 +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.netty.request.body; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; - - -public class NettyByteArrayBody extends NettyDirectBody { - - 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; - } - - @Override - public long getContentLength() { - return bytes.length; - } - - @Override - public String getContentType() { - return contentType; - } - - @Override - public ByteBuf byteBuf() { - return Unpooled.wrappedBuffer(bytes); - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufferBody.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufferBody.java deleted file mode 100644 index 4b06d65895..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyByteBufferBody.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2015 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.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 String contentType; - private final long length; - - public NettyByteBufferBody(ByteBuffer bb) { - this(bb, null); - } - - public NettyByteBufferBody(ByteBuffer bb, String contentType) { - this.bb = bb; - length = bb.remaining(); - bb.mark(); - this.contentType = contentType; - } - - @Override - public long getContentLength() { - return length; - } - - @Override - public String getContentType() { - return contentType; - } - - @Override - public ByteBuf byteBuf() { - // for retry - bb.reset(); - return Unpooled.wrappedBuffer(bb); - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.java deleted file mode 100644 index 4a1f60183b..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyCompositeByteArrayBody.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.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 String contentType; - private final long contentLength; - - public NettyCompositeByteArrayBody(List bytes) { - this(bytes, null); - } - - public NettyCompositeByteArrayBody(List bytes, String contentType) { - this.bytes = new byte[bytes.size()][]; - bytes.toArray(this.bytes); - this.contentType = contentType; - long l = 0; - for (byte[] b : bytes) - l += b.length; - contentLength = l; - } - - @Override - public long getContentLength() { - return contentLength; - } - - @Override - public String getContentType() { - return contentType; - } - - @Override - public ByteBuf byteBuf() { - return Unpooled.wrappedBuffer(bytes); - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.java deleted file mode 100644 index caa8fbefb8..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyDirectBody.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.netty.request.body; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; - -import java.io.IOException; - -import org.asynchttpclient.netty.NettyResponseFuture; - -public abstract class NettyDirectBody implements NettyBody { - - public abstract ByteBuf byteBuf(); - - @Override - public void write(Channel channel, NettyResponseFuture future) throws IOException { - throw new UnsupportedOperationException("This kind of body is supposed to be writen directly"); - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java deleted file mode 100755 index e3270d2ea0..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyFileBody.java +++ /dev/null @@ -1,97 +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.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.netty.NettyResponseFuture; -import org.asynchttpclient.netty.channel.ChannelManager; -import org.asynchttpclient.netty.request.ProgressListener; - -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; - } - - 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) throws IOException { - final RandomAccessFile raf = new RandomAccessFile(file, "r"); - - try { - ChannelFuture writeFuture; - if (ChannelManager.isSslHandlerConfigured(channel.pipeline()) || config.isDisableZeroCopy()) { - writeFuture = channel.write(new ChunkedFile(raf, offset, length, config.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/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.java deleted file mode 100755 index edccb9ca70..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyInputStreamBody.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.netty.request.body; - -import static org.asynchttpclient.util.MiscUtils.closeSilently; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.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; - private final AsyncHttpClientConfig config; - - public NettyInputStreamBody(InputStream inputStream, AsyncHttpClientConfig config) { - this.inputStream = inputStream; - this.config = config; - } - - 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) 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/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/request/body/NettyMultipartBody.java deleted file mode 100755 index 4786e84690..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/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.netty.request.body; - -import static org.asynchttpclient.request.body.multipart.MultipartUtils.newMultipartBody; - -import java.util.List; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.request.body.multipart.MultipartBody; -import org.asynchttpclient.request.body.multipart.Part; - -public class NettyMultipartBody extends NettyBodyBody { - - private final String contentType; - - public NettyMultipartBody(List parts, FluentCaseInsensitiveStringsMap headers, AsyncHttpClientConfig config) { - this(newMultipartBody(parts, headers), config); - } - - private NettyMultipartBody(MultipartBody body, AsyncHttpClientConfig config) { - super(body, config); - contentType = body.getContentType(); - } - - @Override - public String getContentType() { - return contentType; - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java deleted file mode 100755 index 715afadf6b..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/timeout/ReadTimeoutTimerTask.java +++ /dev/null @@ -1,71 +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.netty.timeout; - -import static org.asynchttpclient.util.DateUtils.millisTime; -import io.netty.util.Timeout; - -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.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) { - // idleConnectTimeout 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; - } - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.java deleted file mode 100755 index 42ba4f16b7..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/timeout/RequestTimeoutTimerTask.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.netty.timeout; - -import static org.asynchttpclient.util.DateUtils.millisTime; -import io.netty.util.Timeout; - -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.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 readTimeout 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); - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java deleted file mode 100755 index 1c411a7fa5..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/timeout/TimeoutTimerTask.java +++ /dev/null @@ -1,59 +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.netty.timeout; - -import io.netty.util.TimerTask; - -import java.net.SocketAddress; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.asynchttpclient.netty.NettyResponseFuture; -import org.asynchttpclient.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 - SocketAddress sa = nettyResponseFuture.getChannelRemoteAddress(); - remoteAddress = sa != null ? sa.toString() : "not-connected"; - } - - 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/netty4/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/timeout/TimeoutsHolder.java deleted file mode 100755 index 6d424c23b5..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/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.netty.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/netty4/src/main/java/org/asynchttpclient/netty/util/ByteBufUtils.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/util/ByteBufUtils.java deleted file mode 100755 index dc803e7283..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/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.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/netty4/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java b/providers/netty4/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java deleted file mode 100755 index 57bc318907..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java +++ /dev/null @@ -1,324 +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.netty.ws; - -import static io.netty.buffer.Unpooled.wrappedBuffer; -import static java.nio.charset.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.net.SocketAddress; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.ConcurrentLinkedQueue; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.netty.NettyResponseBodyPart; -import org.asynchttpclient.ws.WebSocket; -import org.asynchttpclient.ws.WebSocketByteFragmentListener; -import org.asynchttpclient.ws.WebSocketByteListener; -import org.asynchttpclient.ws.WebSocketCloseCodeReasonListener; -import org.asynchttpclient.ws.WebSocketListener; -import org.asynchttpclient.ws.WebSocketPingListener; -import org.asynchttpclient.ws.WebSocketPongListener; -import org.asynchttpclient.ws.WebSocketTextFragmentListener; -import org.asynchttpclient.ws.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, AsyncHttpClientConfig config) { - this(channel, config, new ConcurrentLinkedQueue()); - } - - public NettyWebSocket(Channel channel, AsyncHttpClientConfig config, Collection listeners) { - this.channel = channel; - this.listeners = listeners; - maxBufferSize = config.getWebSocketMaxBufferSize(); - } - - @Override - public SocketAddress getRemoteAddress() { - return channel.remoteAddress(); - } - - @Override - public SocketAddress getLocalAddress() { - return channel.localAddress(); - } - - @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 sendMessage(String message) { - channel.writeAndFlush(new TextWebSocketFrame(message)); - return this; - } - - @Override - public WebSocket stream(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/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncHttpProviderTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncHttpProviderTest.java deleted file mode 100644 index 3e30b1df1a..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; - -public class NettyAsyncHttpProviderTest extends AbstractBasicTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncProviderBasicTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncProviderBasicTest.java deleted file mode 100644 index 0aaaa6ce86..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncProviderBasicTest.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.netty; - -import io.netty.channel.ChannelOption; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.AsyncProvidersBasicTest; -import org.testng.annotations.Test; - -@Test -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); - } -} \ No newline at end of file diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncProviderPipelineTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncProviderPipelineTest.java deleted file mode 100644 index f6b28b1e65..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncProviderPipelineTest.java +++ /dev/null @@ -1,89 +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.netty; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig.AdditionalPipelineInitializer; -import org.testng.annotations.Test; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.channel.ChannelPipeline; -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.setHttpAdditionalPipelineInitializer(new AdditionalPipelineInitializer() { - public void initPipeline(ChannelPipeline pipeline) throws Exception { - pipeline.addBefore("inflater", "copyEncodingHeader", new CopyEncodingHandler()); - } - }); - - try (AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder() - .setAsyncHttpClientProviderConfig(nettyConfig).build())) { - 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"); - } - } - } - - 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/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.java deleted file mode 100644 index dbc49ca17d..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncResponseTest.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.netty; - -import static org.testng.Assert.*; - -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.cookie.Cookie; -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, null), new HttpResponseHeaders() { - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); - } - }, null); - - List cookies = response.getCookies(); - assertEquals(cookies.size(), 1); - - Cookie cookie = cookies.get(0); - assertTrue(cookie.getMaxAge() >= 58 && cookie.getMaxAge() <= 60); - } - - @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, null), new HttpResponseHeaders() { - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); - } - }, 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, null), new HttpResponseHeaders() { - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); - } - }, null); - - List cookies = response.getCookies(); - assertEquals(cookies.size(), 1); - - Cookie cookie = cookies.get(0); - assertEquals(cookie.getMaxAge(), Long.MIN_VALUE); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncStreamHandlerTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncStreamHandlerTest.java deleted file mode 100644 index e03efb1f40..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncStreamHandlerTest; - -public class NettyAsyncStreamHandlerTest extends AsyncStreamHandlerTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncStreamLifecycleTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAsyncStreamLifecycleTest.java deleted file mode 100644 index c66e9d250c..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncStreamLifecycleTest; - -public class NettyAsyncStreamLifecycleTest extends AsyncStreamLifecycleTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAuthTimeoutTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyAuthTimeoutTest.java deleted file mode 100644 index 89e25b215e..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AuthTimeoutTest; - -public class NettyAuthTimeoutTest extends AuthTimeoutTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyBasicAuthTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyBasicAuthTest.java deleted file mode 100644 index 0b9db54af8..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyBasicAuthTest.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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.BasicAuthTest; - -public class NettyBasicAuthTest extends BasicAuthTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyBasicHttpsTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyBasicHttpsTest.java deleted file mode 100644 index 9fe45ee2b7..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.BasicHttpsTest; - -public class NettyBasicHttpsTest extends BasicHttpsTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} \ No newline at end of file diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyBodyDeferringAsyncHandlerTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyBodyDeferringAsyncHandlerTest.java deleted file mode 100644 index b9ab2b6d05..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.handler.BodyDeferringAsyncHandlerTest; - -public class NettyBodyDeferringAsyncHandlerTest extends BodyDeferringAsyncHandlerTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyByteBufferCapacityTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyByteBufferCapacityTest.java deleted file mode 100644 index e38a63e1eb..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ByteBufferCapacityTest; - -public class NettyByteBufferCapacityTest extends ByteBufferCapacityTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyComplexClientTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyComplexClientTest.java deleted file mode 100644 index d055f91317..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ComplexClientTest; - -public class NettyComplexClientTest extends ComplexClientTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyConnectionPoolTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyConnectionPoolTest.java deleted file mode 100644 index d53f39def4..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyConnectionPoolTest.java +++ /dev/null @@ -1,121 +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.netty; - -import static org.asynchttpclient.test.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.channel.pool.ConnectionPoolTest; -import org.asynchttpclient.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.netty.channel.pool.ChannelPool; -import org.asynchttpclient.netty.channel.pool.NoopChannelPool; -import org.testng.annotations.Test; - -import java.net.ConnectException; -import java.util.concurrent.TimeUnit; - -import io.netty.channel.Channel; - -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 NoopChannelPool() { - - @Override - public boolean offer(Channel connection, Object partitionKey) { - return false; - } - - @Override - public boolean isOpen() { - return false; - } - }; - - NettyAsyncHttpProviderConfig providerConfig = new NettyAsyncHttpProviderConfig(); - providerConfig.setChannelPool(cp); - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAsyncHttpClientProviderConfig(providerConfig) - .build())) { - Exception exception = null; - try { - client.prepareGet(getTargetUrl()).execute().get(TIMEOUT, TimeUnit.SECONDS); - } catch (Exception ex) { - ex.printStackTrace(); - exception = ex; - } - assertNotNull(exception); - assertNotNull(exception.getCause()); - assertEquals(exception.getCause().getMessage(), "Pool is already closed"); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void testValidConnectionsPool() { - ChannelPool cp = new NoopChannelPool() { - - @Override - public boolean offer(Channel connection, Object partitionKey) { - return true; - } - }; - - NettyAsyncHttpProviderConfig providerConfig = new NettyAsyncHttpProviderConfig(); - providerConfig.setChannelPool(cp); - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAsyncHttpClientProviderConfig(providerConfig) - .build())) { - Exception exception = null; - try { - client.prepareGet(getTargetUrl()).execute().get(TIMEOUT, TimeUnit.SECONDS); - } catch (Exception ex) { - ex.printStackTrace(); - exception = ex; - } - assertNull(exception); - } - } - - @Test - public void testHostNotContactable() { - - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().build())) { - 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); - } - } - } - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyDigestAuthTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyDigestAuthTest.java deleted file mode 100644 index cc48a025c4..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.DigestAuthTest; - -public class NettyDigestAuthTest extends DigestAuthTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyErrorResponseTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyErrorResponseTest.java deleted file mode 100644 index b8a899ce4d..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ErrorResponseTest; - -public class NettyErrorResponseTest extends ErrorResponseTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyExpect100ContinueTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyExpect100ContinueTest.java deleted file mode 100644 index 6a5a6ce8f4..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Expect100ContinueTest; - -public class NettyExpect100ContinueTest extends Expect100ContinueTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyFollowingThreadTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyFollowingThreadTest.java deleted file mode 100644 index 43d6707fc8..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FollowingThreadTest; - -public class NettyFollowingThreadTest extends FollowingThreadTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyHead302Test.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyHead302Test.java deleted file mode 100644 index 43f5a0f6d2..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Head302Test; - -public class NettyHead302Test extends Head302Test { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyHttpToHttpsRedirectTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyHttpToHttpsRedirectTest.java deleted file mode 100644 index 8f3d54d8c8..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpToHttpsRedirectTest; - -public class NettyHttpToHttpsRedirectTest extends HttpToHttpsRedirectTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyIdleStateHandlerTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyIdleStateHandlerTest.java deleted file mode 100644 index 590eadb39b..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.IdleStateHandlerTest; - -public class NettyIdleStateHandlerTest extends IdleStateHandlerTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyMultipleHeaderTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyMultipleHeaderTest.java deleted file mode 100644 index cad2dcbde1..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.MultipleHeaderTest; - -public class NettyMultipleHeaderTest extends MultipleHeaderTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyNoNullResponseTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyNoNullResponseTest.java deleted file mode 100644 index 4e133dd514..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.NoNullResponseTest; - -public class NettyNoNullResponseTest extends NoNullResponseTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyNonAsciiContentLengthTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyNonAsciiContentLengthTest.java deleted file mode 100644 index 193ccec42f..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.NonAsciiContentLengthTest; - -public class NettyNonAsciiContentLengthTest extends NonAsciiContentLengthTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyParamEncodingTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyParamEncodingTest.java deleted file mode 100644 index 0315db8cda..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ParamEncodingTest; - -public class NettyParamEncodingTest extends ParamEncodingTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyPerRequestRelative302Test.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyPerRequestRelative302Test.java deleted file mode 100644 index 23ef73899e..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.PerRequestRelative302Test; - -public class NettyPerRequestRelative302Test extends PerRequestRelative302Test { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyPerRequestTimeoutTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyPerRequestTimeoutTest.java deleted file mode 100644 index 41b8f1bbf3..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.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/netty4/src/test/java/org/asynchttpclient/netty/NettyPostRedirectGetTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyPostRedirectGetTest.java deleted file mode 100644 index 18fd29e3ce..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.PostRedirectGetTest; - -public class NettyPostRedirectGetTest extends PostRedirectGetTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyPostWithQSTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyPostWithQSTest.java deleted file mode 100644 index d7bc0eaf72..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.PostWithQSTest; - -public class NettyPostWithQSTest extends PostWithQSTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyProviderUtil.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyProviderUtil.java deleted file mode 100644 index d889242c55..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyProviderUtil.java +++ /dev/null @@ -1,31 +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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.DefaultAsyncHttpClient; -import org.asynchttpclient.netty.NettyAsyncHttpProvider; - -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/netty4/src/test/java/org/asynchttpclient/netty/NettyQueryParametersTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyQueryParametersTest.java deleted file mode 100644 index 77a63d4d0d..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.QueryParametersTest; - -public class NettyQueryParametersTest extends QueryParametersTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyRC10KTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyRC10KTest.java deleted file mode 100644 index 276d21f7fe..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.RC10KTest; - -public class NettyRC10KTest extends RC10KTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyRedirectConnectionUsageTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyRedirectConnectionUsageTest.java deleted file mode 100644 index f7f3846714..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.RedirectConnectionUsageTest; - -public class NettyRedirectConnectionUsageTest extends RedirectConnectionUsageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyRelative302Test.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyRelative302Test.java deleted file mode 100644 index 5bc48614ff..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Relative302Test; - -public class NettyRelative302Test extends Relative302Test { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyRemoteSiteTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyRemoteSiteTest.java deleted file mode 100644 index f15c322564..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.RemoteSiteTest; - -public class NettyRemoteSiteTest extends RemoteSiteTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyRequestThrottleTimeoutTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyRequestThrottleTimeoutTest.java deleted file mode 100644 index e73980600a..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyRequestThrottleTimeoutTest.java +++ /dev/null @@ -1,143 +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.netty; - -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import org.asynchttpclient.AbstractBasicTest; -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -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.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); - - int samples = 10; - - try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setMaxConnections(1).build())) { - 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()).setRequestTimeout(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"); - } - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyRetryRequestTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/NettyRetryRequestTest.java deleted file mode 100644 index e734a19df0..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/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.netty; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.RetryRequestTest; - -public class NettyRetryRequestTest extends RetryRequestTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/RetryNonBlockingIssue.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/RetryNonBlockingIssue.java deleted file mode 100644 index bc1e2a1fd8..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/RetryNonBlockingIssue.java +++ /dev/null @@ -1,216 +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.netty; - -import static org.asynchttpclient.test.TestUtils.findFreePort; -import static org.asynchttpclient.test.TestUtils.newJettyHttpServer; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.AbstractBasicTest; -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.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)// - .setConnectTimeout(60000)// - .setRequestTimeout(30000)// - .build(); - - try (AsyncHttpClient client = getAsyncHttpClient(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"); - 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(); - } - } - - @Test - public void testRetryNonBlockingAsyncConnect() throws IOException, InterruptedException, ExecutionException { - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setAllowPoolingConnections(true)// - .setMaxConnections(100)// - .setConnectTimeout(60000)// - .setRequestTimeout(30000)// - .build(); - - try (AsyncHttpClient client = getAsyncHttpClient(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"); - 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(); - } - } - - @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/netty4/src/test/java/org/asynchttpclient/netty/channel/NettyMaxConnectionsInThreads.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/channel/NettyMaxConnectionsInThreads.java deleted file mode 100644 index 55a1da6876..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/channel/NettyMaxConnectionsInThreads.java +++ /dev/null @@ -1,24 +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.netty.channel; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.channel.MaxConnectionsInThreads; -import org.asynchttpclient.netty.NettyProviderUtil; - -public class NettyMaxConnectionsInThreads extends MaxConnectionsInThreads { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/channel/NettyMaxTotalConnectionTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/channel/NettyMaxTotalConnectionTest.java deleted file mode 100644 index e8faf39900..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/channel/NettyMaxTotalConnectionTest.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.netty.channel; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.channel.MaxTotalConnectionTest; -import org.asynchttpclient.netty.NettyProviderUtil; - -public class NettyMaxTotalConnectionTest extends MaxTotalConnectionTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/filter/NettyFilterTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/filter/NettyFilterTest.java deleted file mode 100644 index a1854eea14..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/filter/NettyFilterTest.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.netty.filter; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.filter.FilterTest; -import org.asynchttpclient.netty.NettyProviderUtil; -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/netty4/src/test/java/org/asynchttpclient/netty/handler/NettyListenableFutureTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/handler/NettyListenableFutureTest.java deleted file mode 100644 index 94ebd845e7..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/handler/NettyListenableFutureTest.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.netty.handler; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ListenableFutureTest; -import org.asynchttpclient.netty.NettyProviderUtil; - -public class NettyListenableFutureTest extends ListenableFutureTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/ntlm/NettyNtlmTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/ntlm/NettyNtlmTest.java deleted file mode 100644 index 48a97d0e36..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/ntlm/NettyNtlmTest.java +++ /dev/null @@ -1,29 +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.netty.ntlm; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.ntlm.NtlmTest; -import org.testng.annotations.Test; - -@Test -public class NettyNtlmTest extends NtlmTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/proxy/NettyProxyTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/proxy/NettyProxyTest.java deleted file mode 100644 index 43c37e567f..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/proxy/NettyProxyTest.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.netty.proxy; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.proxy.ProxyTest; - -public class NettyProxyTest extends ProxyTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/proxy/NettyProxyTunnellingTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/proxy/NettyProxyTunnellingTest.java deleted file mode 100644 index 1086a91b65..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/proxy/NettyProxyTunnellingTest.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.netty.proxy; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.proxy.ProxyTunnellingTest; - -public class NettyProxyTunnellingTest extends ProxyTunnellingTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyBodyChunkTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyBodyChunkTest.java deleted file mode 100644 index 9a0f316d50..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyBodyChunkTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.BodyChunkTest; - -public class NettyBodyChunkTest extends BodyChunkTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyChunkingTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyChunkingTest.java deleted file mode 100644 index 02a756c96a..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyChunkingTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.asynchttpclient.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.ChunkingTest; - -public class NettyChunkingTest extends ChunkingTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyEmptyBodyTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyEmptyBodyTest.java deleted file mode 100644 index 52c8464546..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyEmptyBodyTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.EmptyBodyTest; - -public class NettyEmptyBodyTest extends EmptyBodyTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyFastUnauthorizedUploadTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyFastUnauthorizedUploadTest.java deleted file mode 100644 index 9b6eae7ad5..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyFastUnauthorizedUploadTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.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/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyFilePartLargeFileTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyFilePartLargeFileTest.java deleted file mode 100644 index 6265c04e44..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyFilePartLargeFileTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.FilePartLargeFileTest; - -public class NettyFilePartLargeFileTest extends FilePartLargeFileTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyInputStreamTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyInputStreamTest.java deleted file mode 100644 index 075a935fed..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyInputStreamTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.InputStreamTest; - -public class NettyInputStreamTest extends InputStreamTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyPutLargeFileTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyPutLargeFileTest.java deleted file mode 100644 index e1e3394da1..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyPutLargeFileTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.PutLargeFileTest; - -public class NettyPutLargeFileTest extends PutLargeFileTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyTransferListenerTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyTransferListenerTest.java deleted file mode 100644 index 3c22546f82..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyTransferListenerTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.TransferListenerTest; - -public class NettyTransferListenerTest extends TransferListenerTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyZeroCopyFileTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyZeroCopyFileTest.java deleted file mode 100644 index 6db5e35735..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/NettyZeroCopyFileTest.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.netty.request.body; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.ZeroCopyFileTest; - -public class NettyZeroCopyFileTest extends ZeroCopyFileTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/multipart/NettyMultipartUploadTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/multipart/NettyMultipartUploadTest.java deleted file mode 100644 index 2b2454b595..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/request/body/multipart/NettyMultipartUploadTest.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.netty.request.body.multipart; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.request.body.multipart.MultipartUploadTest; - -/** - * @author dominict - */ -public class NettyMultipartUploadTest extends MultipartUploadTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/simple/NettySimpleAsyncClientErrorBehaviourTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/simple/NettySimpleAsyncClientErrorBehaviourTest.java deleted file mode 100644 index 0cb6b0babc..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/simple/NettySimpleAsyncClientErrorBehaviourTest.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.netty.simple; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.simple.SimpleAsyncClientErrorBehaviourTest; - -public class NettySimpleAsyncClientErrorBehaviourTest extends SimpleAsyncClientErrorBehaviourTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/simple/NettySimpleAsyncHttpClientTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/simple/NettySimpleAsyncHttpClientTest.java deleted file mode 100644 index d6a2ee317b..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/simple/NettySimpleAsyncHttpClientTest.java +++ /dev/null @@ -1,18 +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.netty.simple; - -import org.asynchttpclient.simple.SimpleAsyncHttpClientTest; - -public class NettySimpleAsyncHttpClientTest extends SimpleAsyncHttpClientTest { -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/webdav/NettyWebDavBasicTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/webdav/NettyWebDavBasicTest.java deleted file mode 100644 index d7512671fb..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/webdav/NettyWebDavBasicTest.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.netty.webdav; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.webdav.WebDavBasicTest; - -public class NettyWebDavBasicTest extends WebDavBasicTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/NettyByteMessageTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/NettyByteMessageTest.java deleted file mode 100644 index e93fcdf4aa..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/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.netty.ws; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.ws.ByteMessageTest; - -public class NettyByteMessageTest extends ByteMessageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/NettyCloseCodeReasonMsgTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/NettyCloseCodeReasonMsgTest.java deleted file mode 100644 index 3428a6a713..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/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.netty.ws; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.ws.CloseCodeReasonMessageTest; - -public class NettyCloseCodeReasonMsgTest extends CloseCodeReasonMessageTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/NettyProxyTunnellingTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/NettyProxyTunnellingTest.java deleted file mode 100644 index b5749300ab..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/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.netty.ws; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.ws.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/netty4/src/test/java/org/asynchttpclient/netty/ws/NettyRedirectTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/NettyRedirectTest.java deleted file mode 100644 index 8afa593932..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/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.netty.ws; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.ws.RedirectTest; - -public class NettyRedirectTest extends RedirectTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/NettyTextMessageTest.java b/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/NettyTextMessageTest.java deleted file mode 100644 index 6c351283c2..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/netty/ws/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.netty.ws; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.netty.NettyProviderUtil; -import org.asynchttpclient.ws.TextMessageTest; - -public class NettyTextMessageTest extends TextMessageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/resources/300k.png b/providers/netty4/src/test/resources/300k.png deleted file mode 100644 index bff4a85989..0000000000 Binary files a/providers/netty4/src/test/resources/300k.png and /dev/null differ diff --git a/providers/netty4/src/test/resources/SimpleTextFile.txt b/providers/netty4/src/test/resources/SimpleTextFile.txt deleted file mode 100644 index 088788f821..0000000000 --- a/providers/netty4/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/netty4/src/test/resources/client.keystore b/providers/netty4/src/test/resources/client.keystore deleted file mode 100644 index eaf8339f44..0000000000 Binary files a/providers/netty4/src/test/resources/client.keystore and /dev/null differ diff --git a/providers/netty4/src/test/resources/gzip.txt.gz b/providers/netty4/src/test/resources/gzip.txt.gz deleted file mode 100644 index 80aeb98d2b..0000000000 Binary files a/providers/netty4/src/test/resources/gzip.txt.gz and /dev/null differ diff --git a/providers/netty4/src/test/resources/logback-test.xml b/providers/netty4/src/test/resources/logback-test.xml deleted file mode 100644 index 4acf278710..0000000000 --- a/providers/netty4/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/netty4/src/test/resources/realm.properties b/providers/netty4/src/test/resources/realm.properties deleted file mode 100644 index bc9faad66a..0000000000 --- a/providers/netty4/src/test/resources/realm.properties +++ /dev/null @@ -1 +0,0 @@ -user=admin, admin \ No newline at end of file diff --git a/providers/netty4/src/test/resources/ssltest-cacerts.jks b/providers/netty4/src/test/resources/ssltest-cacerts.jks deleted file mode 100644 index 207b9646e6..0000000000 Binary files a/providers/netty4/src/test/resources/ssltest-cacerts.jks and /dev/null differ diff --git a/providers/netty4/src/test/resources/ssltest-keystore.jks b/providers/netty4/src/test/resources/ssltest-keystore.jks deleted file mode 100644 index 70267836e8..0000000000 Binary files a/providers/netty4/src/test/resources/ssltest-keystore.jks and /dev/null differ diff --git a/providers/netty4/src/test/resources/textfile.txt b/providers/netty4/src/test/resources/textfile.txt deleted file mode 100644 index 87daee60a9..0000000000 --- a/providers/netty4/src/test/resources/textfile.txt +++ /dev/null @@ -1 +0,0 @@ -filecontent: hello \ No newline at end of file diff --git a/providers/netty4/src/test/resources/textfile2.txt b/providers/netty4/src/test/resources/textfile2.txt deleted file mode 100644 index 6a91fe609c..0000000000 --- a/providers/netty4/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 822983c23c..0000000000 --- a/providers/pom.xml +++ /dev/null @@ -1,63 +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 - - - - - - - - - netty3 - netty4 - - - - - org.asynchttpclient - async-http-client-api - ${project.version} - - - org.asynchttpclient - async-http-client-api - ${project.version} - test - tests - - -